From 8cd95c20c984fed1e1f647d9399a3a6949c6dc6c Mon Sep 17 00:00:00 2001 From: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> Date: Wed, 27 Nov 2024 13:51:47 -0600 Subject: [PATCH 01/69] Update MA getting started (#8797) * Update MA getting started. Signed-off-by: Archer * Apply suggestions from code review Co-authored-by: Peter Nied Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Peter Nied Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Apply suggestions from code review Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Update getting-started-data-migration.md Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Apply suggestions from code review Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Update getting-started-data-migration.md Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> --------- Signed-off-by: Archer Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> Co-authored-by: Peter Nied Co-authored-by: Nathan Bower --- _migrations/getting-started-data-migration.md | 331 ++++++++++++++++++ _migrations/quick-start-data-migration.md | 262 -------------- 2 files changed, 331 insertions(+), 262 deletions(-) create mode 100644 _migrations/getting-started-data-migration.md delete mode 100644 _migrations/quick-start-data-migration.md diff --git a/_migrations/getting-started-data-migration.md b/_migrations/getting-started-data-migration.md new file mode 100644 index 0000000000..8ae1a7f457 --- /dev/null +++ b/_migrations/getting-started-data-migration.md @@ -0,0 +1,331 @@ +--- +layout: default +title: Quickstart: Data migration +nav_order: 10 +--- + +# Getting started: Data migration + +This quickstart outlines how to deploy Migration Assistant for OpenSearch and execute an existing data migration using `Reindex-from-Snapshot` (RFS). It uses AWS for illustrative purposes. However, the steps can be modified for use with other cloud providers. + + +## Prerequisites and assumptions + +Before using this quickstart, make sure you fulfill the following prerequisites: + +* Verify that your migration path [is supported](https://opensearch.org/docs/latest/migrations/is-migration-assistant-right-for-you/#supported-migration-paths). Note that we test with the exact versions specified, but you should be able to migrate data on alternative minor versions as long as the major version is supported. +* The source cluster must be deployed Amazon Simple Storage Service (Amazon S3) plugin. +* The target cluster must be deployed. + +The steps in this guide assume the following: + +* In this guide, a snapshot will be taken and stored in Amazon S3; the following assumptions are made about this snapshot: + * The `_source` flag is enabled on all indexes to be migrated. + * The snapshot includes the global cluster state (`include_global_state` is `true`). + * Shard sizes of up to approximately 80 GB are supported. Larger shards cannot be migrated. If this presents challenges for your migration, contact the [migration team](https://opensearch.slack.com/archives/C054JQ6UJFK). +* Migration Assistant will be installed in the same AWS Region and have access to both the source snapshot and target cluster. + +--- + +## Step 1: Installing Bootstrap on an Amazon EC2 instance (~10 minutes) + +To begin your migration, use the following steps to install a `bootstrap` box on an Amazon Elastic Compute Cloud (Amazon EC2) instance. The instance uses AWS CloudFormation to create and manage the stack. + +1. Log in to the target AWS account in which you want to deploy Migration Assistant. +2. From the browser where you are logged in to your target AWS account, right-click [here](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new?templateURL=https://solutions-reference.s3.amazonaws.com/migration-assistant-for-amazon-opensearch-service/latest/migration-assistant-for-amazon-opensearch-service.template&redirectId=SolutionWeb) to load the CloudFormation template from a new browser tab. +3. Follow the CloudFormation stack wizard: + * **Stack Name:** `MigrationBootstrap` + * **Stage Name:** `dev` + * Choose **Next** after each step > **Acknowledge** > **Submit**. +4. Verify that the Bootstrap stack exists and is set to `CREATE_COMPLETE`. This process takes around 10 minutes to complete. + +--- + +## Step 2: Setting up Bootstrap instance access (~5 minutes) + +Use the following steps to set up Bootstrap instance access: + +1. After deployment, find the EC2 instance ID for the `bootstrap-dev-instance`. +2. Create an AWS Identity and Access Management (IAM) policy using the following snippet, replacing ``, ``, ``, and `` with your information: + + ```json + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "ssm:StartSession", + "Resource": [ + "arn:aws:ec2:::instance/", + "arn:aws:ssm:::document/BootstrapShellDoc--" + ] + } + ] + } + ``` + +3. Name the policy, for example, `SSM-OSMigrationBootstrapAccess`, and then create the policy by selecting **Create policy**. + +--- + +## Step 3: Logging in to Bootstrap and building Migration Assistant (~15 minutes) + +Next, log in to Bootstrap and build Migration Assistant using the following steps. + +### Prerequisites + +To use these steps, make sure you fulfill the following prerequisites: + +* The AWS Command Line Interface (AWS CLI) and AWS Session Manager plugin are installed on your instance. +* The AWS credentials are configured (`aws configure`) for your instance. + +### Steps + +1. Load AWS credentials into your terminal. +2. Log in to the instance using the following command, replacing `` and `` with your instance ID and Region: + + ```bash + aws ssm start-session --document-name BootstrapShellDoc-- --target --region [--profile ] + ``` + +3. Once logged in, run the following command from the shell of the Bootstrap instance in the `/opensearch-migrations` directory: + + ```bash + ./initBootstrap.sh && cd deployment/cdk/opensearch-service-migration + ``` + +4. After a successful build, note the path for infrastructure deployment, which will be used in the next step. + +--- + +## Step 4: Configuring and deploying RFS (~20 minutes) + +Use the following steps to configure and deploy RFS: + +1. Add the target cluster password to AWS Secrets Manager as an unstructured string. Be sure to copy the secret Amazon Resource Name (ARN) for use during deployment. +2. From the same shell as the Bootstrap instance, modify the `cdk.context.json` file located in the `/opensearch-migrations/deployment/cdk/opensearch-service-migration` directory: + + ```json + { + "migration-assistant": { + "vpcId": "", + "targetCluster": { + "endpoint": "", + "auth": { + "type": "basic", + "username": "", + "passwordFromSecretArn": "" + } + }, + "sourceCluster": { + "endpoint": "", + "auth": { + "type": "basic", + "username": "", + "passwordFromSecretArn": "" + } + }, + "reindexFromSnapshotExtraArgs": "", + "stage": "dev", + "otelCollectorEnabled": true, + "migrationConsoleServiceEnabled": true, + "reindexFromSnapshotServiceEnabled": true, + "migrationAssistanceEnabled": true + } + } + ``` + + The source and target cluster authorization can be configured to have no authorization, `basic` with a username and password, or `sigv4`. + +3. Bootstrap the account with the following command: + + ```bash + cdk bootstrap --c contextId=migration-assistant --require-approval never + ``` + +4. Deploy the stacks: + + ```bash + cdk deploy "*" --c contextId=migration-assistant --require-approval never --concurrency 5 + ``` + +5. Verify that all CloudFormation stacks were installed successfully. + +### RFS parameters + +If you're creating a snapshot using migration tooling, these parameters are automatically configured. If you're using an existing snapshot, modify the `reindexFromSnapshotExtraArgs` setting with the following values: + + ```bash + --s3-repo-uri s3:/// --s3-region --snapshot-name + ``` + +You will also need to give the `migrationconsole` and `reindexFromSnapshot` TaskRoles permissions to the S3 bucket. + +--- + +## Step 5: Deploying Migration Assistant + +To deploy Migration Assistant, use the following steps: + +1. Bootstrap the account: + + ```bash + cdk bootstrap --c contextId=migration-assistant --require-approval never --concurrency 5 + ``` +2. Deploy the stacks when `cdk.context.json` is fully configured: + + ```bash + cdk deploy "*" --c contextId=migration-assistant --require-approval never --concurrency 3 + ``` + +These commands deploy the following stacks: + +* Migration Assistant network stack +* Reindex From Snapshot stack +* Migration console stack + +--- + +## Step 6: Accessing the migration console + +Run the following command to access the migration console: + +```bash +./accessContainer.sh migration-console dev +``` + + +`accessContainer.sh` is located in `/opensearch-migrations/deployment/cdk/opensearch-service-migration/` on the Bootstrap instance. To learn more, see [Accessing the migration console]. +`{: .note} + +--- + +## Step 7: Verifying the connection to the source and target clusters + +To verify the connection to the clusters, run the following command: + +```bash +console clusters connection-check +``` + +You should receive the following output: + +``` +* **Source Cluster:** Successfully connected! +* **Target Cluster:** Successfully connected! +``` + +To learn more about migration console commands, see [Migration commands]. + +--- + +## Step 8: Snapshot creation + +Run the following command to initiate snapshot creation from the source cluster: + +```bash +console snapshot create [...] +``` + +To check the snapshot creation status, run the following command: + +```bash +console snapshot status [...] +``` + +To learn more information about the snapshot, run the following command: + +```bash +console snapshot status --deep-check [...] +``` + +Wait for snapshot creation to complete before moving to step 9. + +To learn more about snapshot creation, see [Snapshot Creation]. + +--- + +## Step 9: Metadata migration + +Run the following command to migrate metadata: + +```bash +console metadata migrate [...] +``` + +For more information, see [Metadata migration]. + +--- + +## Step 10: RFS document migration + +You can now use RFS to migrate documents from your original cluster: + +1. To start the migration from RFS, start a `backfill` using the following command: + + ```bash + console backfill start + ``` + +2. _(Optional)_ To speed up the migration, increase the number of documents processed at a simultaneously by using the following command: + + ```bash + console backfill scale + ``` + +3. To check the status of the documentation backfill, use the following command: + + ```bash + console backfill status + ``` + +4. If you need to stop the backfill process, use the following command: + + ```bash + console backfill stop + ``` + +For more information, see [Backfill execution]. + +--- + +## Step 11: Backfill monitoring + +Use the following command for detailed monitoring of the backfill process: + +```bash +console backfill status --deep-check +``` + +You should receive the following output: + +```json +BackfillStatus.RUNNING +Running=9 +Pending=1 +Desired=10 +Shards total: 62 +Shards completed: 46 +Shards incomplete: 16 +Shards in progress: 11 +Shards unclaimed: 5 +``` + +Logs and metrics are available in Amazon CloudWatch in the `OpenSearchMigrations` log group. + +--- + +## Step 12: Verify that all documents were migrated + +Use the following query in CloudWatch Logs Insights to identify failed documents: + +```bash +fields @message +| filter @message like "Bulk request succeeded, but some operations failed." +| sort @timestamp desc +| limit 10000 +``` + +If any failed documents are identified, you can index the failed documents directly as opposed to using RFS. + +For more information, see [Backfill migration]. diff --git a/_migrations/quick-start-data-migration.md b/_migrations/quick-start-data-migration.md deleted file mode 100644 index 62b13292e7..0000000000 --- a/_migrations/quick-start-data-migration.md +++ /dev/null @@ -1,262 +0,0 @@ ---- -layout: default -title: Quickstart - Data migration -nav_order: 10 ---- - -# Quickstart - Data migration - -This document outlines how to deploy the Migration Assistant and execute an existing data migration using Reindex-from-Snapshot (RFS). Note that this does not include steps for deploying and capturing live traffic, which is necessary for a zero-downtime migration. Please refer to the "Phases of a Migration" section in the wiki navigation bar for a complete end-to-end migration process, including metadata migration, live capture, Reindex-from-Snapshot, and replay. - -## Prerequisites and Assumptions -* Verify your migration path [is supported](https://github.com/opensearch-project/opensearch-migrations/wiki/Is-Migration-Assistant-Right-for-You%3F#supported-migration-paths). Note that we test with the exact versions specified, but you should be able to migrate data on alternative minor versions as long as the major version is supported. -* Source cluster must be deployed with the S3 plugin. -* Target cluster must be deployed. -* A snapshot will be taken and stored in S3 in this guide, and the following assumptions are made about this snapshot: - * The `_source` flag is enabled on all indices to be migrated. - * The snapshot includes the global cluster state (`include_global_state` is `true`). - * Shard sizes up to approximately 80GB are supported. Larger shards will not be able to migrate. If this is a blocker, please consult the migrations team. -* Migration Assistant will be installed in the same region and have access to both the source snapshot and target cluster. - ---- - -## Step 1 - Installing Bootstrap EC2 Instance (~10 mins) -1. Log into the target AWS account where you want to deploy the Migration Assistant. -2. From the browser where you are logged into your target AWS account right-click [here](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new?templateURL=https://solutions-reference.s3.amazonaws.com/migration-assistant-for-amazon-opensearch-service/latest/migration-assistant-for-amazon-opensearch-service.template&redirectId=SolutionWeb) ↗ to load the CloudFormation (Cfn) template from a new browser tab. -3. Follow the CloudFormation stack wizard: - * **Stack Name:** `MigrationBootstrap` - * **Stage Name:** `dev` - * Hit **Next** on each step, acknowledge on the fourth screen, and hit **Submit**. -4. Verify that the bootstrap stack exists and is set to `CREATE_COMPLETE`. This process takes around 10 minutes. - ---- - -## Step 2 - Setup Bootstrap Instance Access (~5 mins) -1. After deployment, find the EC2 instance ID for the `bootstrap-dev-instance`. -2. Create an IAM policy using the snippet below, replacing ``, ``, ``, and ``: - -```json -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": "ssm:StartSession", - "Resource": [ - "arn:aws:ec2:::instance/", - "arn:aws:ssm:::document/SSM--BootstrapShell" - ] - } - ] -} -``` - -3. Name the policy, e.g., `SSM-OSMigrationBootstrapAccess`, and create the policy. - ---- - -## Step 3 - Login to Bootstrap and Build (~15 mins) -### Prerequisites: -* AWS CLI and AWS Session Manager Plugin installed. -* AWS credentials configured (`aws configure`). - -1. Load AWS credentials into your terminal. -2. Login to the instance using the command below, replacing `` and ``: -```bash -aws ssm start-session --document-name SSM-dev-BootstrapShell --target --region [--profile ] -``` -3. Once logged in, run the following command from the shell of the bootstrap instance (within the /opensearch-migrations directory): -```bash -./initBootstrap.sh && cd deployment/cdk/opensearch-service-migration -``` -4. After a successful build, remember the path for infrastructure deployment in the next step. - ---- - -## Step 4 - Configuring and Deploying for RFS Use Case (~20 mins) -1. Add the target cluster password to AWS Secrets Manager as an unstructured string. Be sure to copy the secret ARN for use during deployment. -2. From the same shell on the bootstrap instance, modify the cdk.context.json file located in the `/opensearch-migrations/deployment/cdk/opensearch-service-migration` directory: - -```json -{ - "migration-assistant": { - "vpcId": "", - "targetCluster": { - "endpoint": "", - "auth": { - "type": "basic", - "username": "", - "passwordFromSecretArn": "" - } - }, - "sourceCluster": { - "endpoint": "", - "auth": { - "type": "basic", - "username": "", - "passwordFromSecretArn": "" - } - }, - "reindexFromSnapshotExtraArgs": "", - "stage": "dev", - "otelCollectorEnabled": true, - "migrationConsoleServiceEnabled": true, - "reindexFromSnapshotServiceEnabled": true, - "migrationAssistanceEnabled": true - } -} -``` - -The source and target cluster authorization can be configured to have none, `basic` with a username and password, or `sigv4`. There are examples of each available [here](https://github.com/opensearch-project/opensearch-migrations/wiki/Configuration-Options#cluster-authentication-options). - -3. Bootstrap the account with the following command: -```bash -cdk bootstrap --c contextId=migration-assistant --require-approval never -``` -4. Deploy the stacks: -```bash -cdk deploy "*" --c contextId=migration-assistant --require-approval never --concurrency 5 -``` -5. Verify that all CloudFormation stacks were installed successfully. - -#### ReindexFromSnapshot Parameters -* If you're creating a snapshot using migration tooling, these parameters are auto-configured. If you're using an existing snapshot, modify `reindexFromSnapshotExtraArgs` with the following values: -```bash ---s3-repo-uri s3:/// --s3-region --snapshot-name -``` -Note, you will also need to give access to the migrationconsole and reindexFromSnapshot taskRole permissions to the bucket - ---- - -## Step 5 - Deploying the Migration Assistant -1. Bootstrap the account: -```bash -cdk bootstrap --c contextId=migration-assistant --require-approval never --concurrency 5 -``` -2. Deploy the stacks when `cdk.context.json` is fully configured: -```bash -cdk deploy "*" --c contextId=migration-assistant --require-approval never --concurrency 3 -``` - -### Stacks Deployed: -* Migration Assistant Network stack -* Reindex From Snapshot stack -* Migration Console stack - ---- - -## Step 6 - Accessing the Migration Console -Run the following command to access the migration console: -```bash -./accessContainer.sh migration-console dev -``` ->[!NOTE] ->`accessContainer.sh` is located in `/opensearch-migrations/deployment/cdk/opensearch-service-migration/` on the bootstrap instance. - -_Learn more [[Accessing the Migration Console]]_ - ---- - -## Step 7 - Checking Connection to Source & Target Clusters -To verify the connection to the clusters, run: -```bash -console clusters connection-check -``` - -### Expected Output: -* **Source Cluster:** Successfully connected! -* **Target Cluster:** Successfully connected! - -_Learn more [[Console commands reference|Migration-Console-commands-references]]_ - ---- - -## Step 8 - Snapshot Creation -Run the following to initiate creating a snapshot from the source cluster -``` -console snapshot create [...] -``` - -To check on the progress, -``` -console snapshot status [...] -``` -or, for more detail, -``` -console snapshot status --deep-check [...] -``` - -Wait for the snapshot to complete before moving to the next step. - -_Learn more [[Snapshot Creation Verification]] [[Snapshot Creation]]_ - ---- - -## Step 9 - Metadata Migration -Run the following command to migrate metadata: -```bash -console metadata migrate [...] -``` - -_Learn more [[Metadata Migration]]_ - ---- - -## Step 10 - RFS Document Migration -Start the backfill process: -```bash -console backfill start -``` - -Scale up the number of workers: -```bash -console backfill scale -``` - -Check the status: -```bash -console backfill status -``` - -To stop the workers: -```bash -console backfill stop -``` - -_Learn more [[Backfill Execution]]_ - ---- - -## Step 11 - Monitoring -Use the following command for detailed monitoring: -```bash -console backfill status --deep-check -``` - -### Example Output: -```text -BackfillStatus.RUNNING -Running=9 -Pending=1 -Desired=10 -Shards total: 62 -Shards completed: 46 -Shards incomplete: 16 -Shards in progress: 11 -Shards unclaimed: 5 -``` - -Logs and metrics are available in CloudWatch in the OpenSearchMigrations log group. - ---- - -## Step 12 - Verify all documents were migrated -Use the following query in CloudWatch Logs Insights to identify failed documents: -```bash -fields @message -| filter @message like "Bulk request succeeded, but some operations failed." -| sort @timestamp desc -| limit 10000 -``` - -_Learn more [[Backfill Result Validation]]_ \ No newline at end of file From 82fcd589f2d4c68fc436dda4eb41d270dbcf3ef8 Mon Sep 17 00:00:00 2001 From: Wenchen Li <9028430+neo@users.noreply.github.com> Date: Fri, 29 Nov 2024 15:07:05 -0500 Subject: [PATCH 02/69] Fix a url (#8824) Signed-off-by: Wenchen Li <9028430+neo@users.noreply.github.com> Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --- _search-plugins/searching-data/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_search-plugins/searching-data/index.md b/_search-plugins/searching-data/index.md index 279958d97c..42ce7654a0 100644 --- a/_search-plugins/searching-data/index.md +++ b/_search-plugins/searching-data/index.md @@ -19,4 +19,4 @@ Feature | Description [Sort results]({{site.url}}{{site.baseurl}}/opensearch/search/sort/) | Allow sorting of results by different criteria. [Highlight query matches]({{site.url}}{{site.baseurl}}/opensearch/search/highlight/) | Highlight the search term in the results. [Retrieve inner hits]({{site.url}}{{site.baseurl}}/search-plugins/searching-data/inner-hits/) | Retrieve underlying hits in nested and parent-join objects. -[Retrieve specific fields]({{site.url}}{{site.baseurl}}search-plugins/searching-data/retrieve-specific-fields/) | Retrieve only the specific fields +[Retrieve specific fields]({{site.url}}{{site.baseurl}}/search-plugins/searching-data/retrieve-specific-fields/) | Retrieve only the specific fields From 2d1d8172b6a6901671b99aa625fdc451faaa4160 Mon Sep 17 00:00:00 2001 From: AntonEliatra Date: Mon, 2 Dec 2024 13:55:03 +0000 Subject: [PATCH 03/69] Add length token filter docs #8152 (#8156) * Add length token filter docs #8152 Signed-off-by: Anton Rubin * Update length.md Signed-off-by: AntonEliatra * updating parameter table Signed-off-by: Anton Rubin * Doc review Signed-off-by: Fanit Kolchina * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --------- Signed-off-by: Anton Rubin Signed-off-by: AntonEliatra Signed-off-by: Fanit Kolchina Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Fanit Kolchina Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Nathan Bower --- _analyzers/token-filters/index.md | 2 +- _analyzers/token-filters/length.md | 91 ++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 _analyzers/token-filters/length.md diff --git a/_analyzers/token-filters/index.md b/_analyzers/token-filters/index.md index 14abeab567..6ff348dd37 100644 --- a/_analyzers/token-filters/index.md +++ b/_analyzers/token-filters/index.md @@ -38,7 +38,7 @@ Token filter | Underlying Lucene token filter| Description `keyword_repeat` | [KeywordRepeatFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/KeywordRepeatFilter.html) | Emits each incoming token twice: once as a keyword and once as a non-keyword. `kstem` | [KStemFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/en/KStemFilter.html) | Provides kstem-based stemming for the English language. Combines algorithmic stemming with a built-in dictionary. `kuromoji_completion` | [JapaneseCompletionFilter](https://lucene.apache.org/core/9_10_0/analysis/kuromoji/org/apache/lucene/analysis/ja/JapaneseCompletionFilter.html) | Adds Japanese romanized terms to the token stream (in addition to the original tokens). Usually used to support autocomplete on Japanese search terms. Note that the filter has a `mode` parameter, which should be set to `index` when used in an index analyzer and `query` when used in a search analyzer. Requires the `analysis-kuromoji` plugin. For information about installing the plugin, see [Additional plugins]({{site.url}}{{site.baseurl}}/install-and-configure/plugins/#additional-plugins). -`length` | [LengthFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/LengthFilter.html) | Removes tokens whose lengths are shorter or longer than the length range specified by `min` and `max`. +[`length`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/length/) | [LengthFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/LengthFilter.html) | Removes tokens that are shorter or longer than the length range specified by `min` and `max`. `limit` | [LimitTokenCountFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/LimitTokenCountFilter.html) | Limits the number of output tokens. A common use case is to limit the size of document field values based on token count. `lowercase` | [LowerCaseFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/core/LowerCaseFilter.html) | Converts tokens to lowercase. The default [LowerCaseFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/core/LowerCaseFilter.html) is for the English language. You can set the `language` parameter to `greek` (uses [GreekLowerCaseFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/el/GreekLowerCaseFilter.html)), `irish` (uses [IrishLowerCaseFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/ga/IrishLowerCaseFilter.html)), or `turkish` (uses [TurkishLowerCaseFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/tr/TurkishLowerCaseFilter.html)). [`min_hash`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/min-hash/) | [MinHashFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/minhash/MinHashFilter.html) | Uses the [MinHash technique](https://en.wikipedia.org/wiki/MinHash) to estimate document similarity. Performs the following operations on a token stream sequentially:
1. Hashes each token in the stream.
2. Assigns the hashes to buckets, keeping only the smallest hashes of each bucket.
3. Outputs the smallest hash from each bucket as a token stream. diff --git a/_analyzers/token-filters/length.md b/_analyzers/token-filters/length.md new file mode 100644 index 0000000000..f6c5dcc706 --- /dev/null +++ b/_analyzers/token-filters/length.md @@ -0,0 +1,91 @@ +--- +layout: default +title: Length +parent: Token filters +nav_order: 240 +--- + +# Length token filter + +The `length` token filter is used to remove tokens that don't meet specified length criteria (minimum and maximum values) from the token stream. + +## Parameters + +The `length` token filter can be configured with the following parameters. + +Parameter | Required/Optional | Data type | Description +:--- | :--- | :--- | :--- +`min` | Optional | Integer | The minimum token length. Default is `0`. +`max` | Optional | Integer | The maximum token length. Default is `Integer.MAX_VALUE` (`2147483647`). + + +## Example + +The following example request creates a new index named `my_index` and configures an analyzer with a `length` filter: + +```json +PUT my_index +{ + "settings": { + "analysis": { + "analyzer": { + "only_keep_4_to_10_characters": { + "tokenizer": "whitespace", + "filter": [ "length_4_to_10" ] + } + }, + "filter": { + "length_4_to_10": { + "type": "length", + "min": 4, + "max": 10 + } + } + } + } +} +``` +{% include copy-curl.html %} + +## Generated tokens + +Use the following request to examine the tokens generated using the analyzer: + +```json +GET /my_index/_analyze +{ + "analyzer": "only_keep_4_to_10_characters", + "text": "OpenSearch is a great tool!" +} +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + { + "token": "OpenSearch", + "start_offset": 0, + "end_offset": 10, + "type": "word", + "position": 0 + }, + { + "token": "great", + "start_offset": 16, + "end_offset": 21, + "type": "word", + "position": 3 + }, + { + "token": "tool!", + "start_offset": 22, + "end_offset": 27, + "type": "word", + "position": 4 + } + ] +} +``` From c0d158f59fb37819919ab3c4a1fee2fa27707423 Mon Sep 17 00:00:00 2001 From: AntonEliatra Date: Mon, 2 Dec 2024 14:38:33 +0000 Subject: [PATCH 04/69] Add lowercase token filter docs (#8162) * add lowercase token filter Signed-off-by: Anton Rubin * adding examples in greek to lowercase token filter #8154 Signed-off-by: Anton Rubin * Update lowercase.md Signed-off-by: AntonEliatra * Doc review Signed-off-by: Fanit Kolchina * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --------- Signed-off-by: Anton Rubin Signed-off-by: AntonEliatra Signed-off-by: Fanit Kolchina Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Fanit Kolchina Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Nathan Bower --- _analyzers/token-filters/index.md | 2 +- _analyzers/token-filters/lowercase.md | 82 +++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 _analyzers/token-filters/lowercase.md diff --git a/_analyzers/token-filters/index.md b/_analyzers/token-filters/index.md index 6ff348dd37..040397424b 100644 --- a/_analyzers/token-filters/index.md +++ b/_analyzers/token-filters/index.md @@ -40,7 +40,7 @@ Token filter | Underlying Lucene token filter| Description `kuromoji_completion` | [JapaneseCompletionFilter](https://lucene.apache.org/core/9_10_0/analysis/kuromoji/org/apache/lucene/analysis/ja/JapaneseCompletionFilter.html) | Adds Japanese romanized terms to the token stream (in addition to the original tokens). Usually used to support autocomplete on Japanese search terms. Note that the filter has a `mode` parameter, which should be set to `index` when used in an index analyzer and `query` when used in a search analyzer. Requires the `analysis-kuromoji` plugin. For information about installing the plugin, see [Additional plugins]({{site.url}}{{site.baseurl}}/install-and-configure/plugins/#additional-plugins). [`length`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/length/) | [LengthFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/LengthFilter.html) | Removes tokens that are shorter or longer than the length range specified by `min` and `max`. `limit` | [LimitTokenCountFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/LimitTokenCountFilter.html) | Limits the number of output tokens. A common use case is to limit the size of document field values based on token count. -`lowercase` | [LowerCaseFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/core/LowerCaseFilter.html) | Converts tokens to lowercase. The default [LowerCaseFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/core/LowerCaseFilter.html) is for the English language. You can set the `language` parameter to `greek` (uses [GreekLowerCaseFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/el/GreekLowerCaseFilter.html)), `irish` (uses [IrishLowerCaseFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/ga/IrishLowerCaseFilter.html)), or `turkish` (uses [TurkishLowerCaseFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/tr/TurkishLowerCaseFilter.html)). +[`lowercase`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/lowercase/) | [LowerCaseFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/core/LowerCaseFilter.html) | Converts tokens to lowercase. The default [LowerCaseFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/core/LowerCaseFilter.html) processes the English language. To process other languages, set the `language` parameter to `greek` (uses [GreekLowerCaseFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/el/GreekLowerCaseFilter.html)), `irish` (uses [IrishLowerCaseFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/ga/IrishLowerCaseFilter.html)), or `turkish` (uses [TurkishLowerCaseFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/tr/TurkishLowerCaseFilter.html)). [`min_hash`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/min-hash/) | [MinHashFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/minhash/MinHashFilter.html) | Uses the [MinHash technique](https://en.wikipedia.org/wiki/MinHash) to estimate document similarity. Performs the following operations on a token stream sequentially:
1. Hashes each token in the stream.
2. Assigns the hashes to buckets, keeping only the smallest hashes of each bucket.
3. Outputs the smallest hash from each bucket as a token stream. [`multiplexer`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/multiplexer/) | N/A | Emits multiple tokens at the same position. Runs each token through each of the specified filter lists separately and outputs the results as separate tokens. [`ngram`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/ngram/) | [NGramTokenFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/ngram/NGramTokenFilter.html) | Tokenizes the given token into n-grams of lengths between `min_gram` and `max_gram`. diff --git a/_analyzers/token-filters/lowercase.md b/_analyzers/token-filters/lowercase.md new file mode 100644 index 0000000000..89f0f219fa --- /dev/null +++ b/_analyzers/token-filters/lowercase.md @@ -0,0 +1,82 @@ +--- +layout: default +title: Lowercase +parent: Token filters +nav_order: 260 +--- + +# Lowercase token filter + +The `lowercase` token filter is used to convert all characters in the token stream to lowercase, making searches case insensitive. + +## Parameters + +The `lowercase` token filter can be configured with the following parameter. + +Parameter | Required/Optional | Description +:--- | :--- | :--- + `language` | Optional | Specifies a language-specific token filter. Valid values are:
- [`greek`](https://lucene.apache.org/core/8_7_0/analyzers-common/org/apache/lucene/analysis/el/GreekLowerCaseFilter.html)
- [`irish`](https://lucene.apache.org/core/8_7_0/analyzers-common/org/apache/lucene/analysis/ga/IrishLowerCaseFilter.html)
- [`turkish`](https://lucene.apache.org/core/8_7_0/analyzers-common/org/apache/lucene/analysis/tr/TurkishLowerCaseFilter.html).
Default is the [Lucene LowerCaseFilter](https://lucene.apache.org/core/8_7_0/analyzers-common/org/apache/lucene/analysis/core/LowerCaseFilter.html). + +## Example + +The following example request creates a new index named `custom_lowercase_example`. It configures an analyzer with a `lowercase` filter and specifies `greek` as the `language`: + +```json +PUT /custom_lowercase_example +{ + "settings": { + "analysis": { + "analyzer": { + "greek_lowercase_example": { + "type": "custom", + "tokenizer": "standard", + "filter": ["greek_lowercase"] + } + }, + "filter": { + "greek_lowercase": { + "type": "lowercase", + "language": "greek" + } + } + } + } +} +``` +{% include copy-curl.html %} + +## Generated tokens + +Use the following request to examine the tokens generated using the analyzer: + +```json +GET /custom_lowercase_example/_analyze +{ + "analyzer": "greek_lowercase_example", + "text": "Αθήνα ΕΛΛΑΔΑ" +} +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + { + "token": "αθηνα", + "start_offset": 0, + "end_offset": 5, + "type": "", + "position": 0 + }, + { + "token": "ελλαδα", + "start_offset": 6, + "end_offset": 12, + "type": "", + "position": 1 + } + ] +} +``` From 1026a847fc4b8966321808c45bdc120de29202f4 Mon Sep 17 00:00:00 2001 From: AntonEliatra Date: Mon, 2 Dec 2024 16:17:26 +0000 Subject: [PATCH 05/69] Add limit token filter docs (#8159) * adding limit token filter docs Signed-off-by: Anton Rubin * removing parameter which is not working #8153 Signed-off-by: Anton Rubin * adding consume_all_tokens Signed-off-by: Anton Rubin * Update limit.md Signed-off-by: AntonEliatra * updating parameter table Signed-off-by: Anton Rubin * Doc review Signed-off-by: Fanit Kolchina * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> * Update _analyzers/token-filters/index.md Co-authored-by: Nathan Bower Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --------- Signed-off-by: Anton Rubin Signed-off-by: AntonEliatra Signed-off-by: Fanit Kolchina Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Fanit Kolchina Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Nathan Bower --- _analyzers/token-filters/index.md | 2 +- _analyzers/token-filters/limit.md | 89 +++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 _analyzers/token-filters/limit.md diff --git a/_analyzers/token-filters/index.md b/_analyzers/token-filters/index.md index 040397424b..07e00dd466 100644 --- a/_analyzers/token-filters/index.md +++ b/_analyzers/token-filters/index.md @@ -39,7 +39,7 @@ Token filter | Underlying Lucene token filter| Description `kstem` | [KStemFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/en/KStemFilter.html) | Provides kstem-based stemming for the English language. Combines algorithmic stemming with a built-in dictionary. `kuromoji_completion` | [JapaneseCompletionFilter](https://lucene.apache.org/core/9_10_0/analysis/kuromoji/org/apache/lucene/analysis/ja/JapaneseCompletionFilter.html) | Adds Japanese romanized terms to the token stream (in addition to the original tokens). Usually used to support autocomplete on Japanese search terms. Note that the filter has a `mode` parameter, which should be set to `index` when used in an index analyzer and `query` when used in a search analyzer. Requires the `analysis-kuromoji` plugin. For information about installing the plugin, see [Additional plugins]({{site.url}}{{site.baseurl}}/install-and-configure/plugins/#additional-plugins). [`length`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/length/) | [LengthFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/LengthFilter.html) | Removes tokens that are shorter or longer than the length range specified by `min` and `max`. -`limit` | [LimitTokenCountFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/LimitTokenCountFilter.html) | Limits the number of output tokens. A common use case is to limit the size of document field values based on token count. +[`limit`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/limit/) | [LimitTokenCountFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/LimitTokenCountFilter.html) | Limits the number of output tokens. For example, document field value sizes can be limited based on the token count. [`lowercase`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/lowercase/) | [LowerCaseFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/core/LowerCaseFilter.html) | Converts tokens to lowercase. The default [LowerCaseFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/core/LowerCaseFilter.html) processes the English language. To process other languages, set the `language` parameter to `greek` (uses [GreekLowerCaseFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/el/GreekLowerCaseFilter.html)), `irish` (uses [IrishLowerCaseFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/ga/IrishLowerCaseFilter.html)), or `turkish` (uses [TurkishLowerCaseFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/tr/TurkishLowerCaseFilter.html)). [`min_hash`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/min-hash/) | [MinHashFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/minhash/MinHashFilter.html) | Uses the [MinHash technique](https://en.wikipedia.org/wiki/MinHash) to estimate document similarity. Performs the following operations on a token stream sequentially:
1. Hashes each token in the stream.
2. Assigns the hashes to buckets, keeping only the smallest hashes of each bucket.
3. Outputs the smallest hash from each bucket as a token stream. [`multiplexer`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/multiplexer/) | N/A | Emits multiple tokens at the same position. Runs each token through each of the specified filter lists separately and outputs the results as separate tokens. diff --git a/_analyzers/token-filters/limit.md b/_analyzers/token-filters/limit.md new file mode 100644 index 0000000000..a849f5f06b --- /dev/null +++ b/_analyzers/token-filters/limit.md @@ -0,0 +1,89 @@ +--- +layout: default +title: Limit +parent: Token filters +nav_order: 250 +--- + +# Limit token filter + +The `limit` token filter is used to limit the number of tokens passed through the analysis chain. + +## Parameters + +The `limit` token filter can be configured with the following parameters. + +Parameter | Required/Optional | Data type | Description +:--- | :--- | :--- | :--- +`max_token_count` | Optional | Integer | The maximum number of tokens to be generated. Default is `1`. +`consume_all_tokens` | Optional | Boolean | (Expert-level setting) Uses all tokens from the tokenizer, even if the result exceeds `max_token_count`. When this parameter is set, the output still only contains the number of tokens specified by `max_token_count`. However, all tokens generated by the tokenizer are processed. Default is `false`. + +## Example + +The following example request creates a new index named `my_index` and configures an analyzer with a `limit` filter: + +```json +PUT my_index +{ + "settings": { + "analysis": { + "analyzer": { + "three_token_limit": { + "tokenizer": "standard", + "filter": [ "custom_token_limit" ] + } + }, + "filter": { + "custom_token_limit": { + "type": "limit", + "max_token_count": 3 + } + } + } + } +} +``` +{% include copy-curl.html %} + +## Generated tokens + +Use the following request to examine the tokens generated using the analyzer: + +```json +GET /my_index/_analyze +{ + "analyzer": "three_token_limit", + "text": "OpenSearch is a powerful and flexible search engine." +} +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + { + "token": "OpenSearch", + "start_offset": 0, + "end_offset": 10, + "type": "", + "position": 0 + }, + { + "token": "is", + "start_offset": 11, + "end_offset": 13, + "type": "", + "position": 1 + }, + { + "token": "a", + "start_offset": 14, + "end_offset": 15, + "type": "", + "position": 2 + } + ] +} +``` From 7bcd36c092b67a7c12a936862e49ab944358a9e1 Mon Sep 17 00:00:00 2001 From: AntonEliatra Date: Mon, 2 Dec 2024 16:23:51 +0000 Subject: [PATCH 06/69] Add reverse token filter docs #8274 (#8395) * adding reverse token filter docs #8274 Signed-off-by: Anton Rubin * Doc review Signed-off-by: Fanit Kolchina * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --------- Signed-off-by: Anton Rubin Signed-off-by: Fanit Kolchina Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Fanit Kolchina Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Nathan Bower --- _analyzers/token-filters/index.md | 2 +- _analyzers/token-filters/reverse.md | 86 +++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 _analyzers/token-filters/reverse.md diff --git a/_analyzers/token-filters/index.md b/_analyzers/token-filters/index.md index 07e00dd466..961be36f85 100644 --- a/_analyzers/token-filters/index.md +++ b/_analyzers/token-filters/index.md @@ -51,7 +51,7 @@ Token filter | Underlying Lucene token filter| Description [`porter_stem`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/porter-stem/) | [PorterStemFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/en/PorterStemFilter.html) | Uses the [Porter stemming algorithm](https://tartarus.org/martin/PorterStemmer/) to perform algorithmic stemming for the English language. [`predicate_token_filter`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/predicate-token-filter/) | N/A | Removes tokens that do not match the specified predicate script. Supports only inline Painless scripts. [`remove_duplicates`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/remove-duplicates/) | [RemoveDuplicatesTokenFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/RemoveDuplicatesTokenFilter.html) | Removes duplicate tokens that are in the same position. -`reverse` | [ReverseStringFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/reverse/ReverseStringFilter.html) | Reverses the string corresponding to each token in the token stream. For example, the token `dog` becomes `god`. +[`reverse`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/reverse/) | [ReverseStringFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/reverse/ReverseStringFilter.html) | Reverses the string corresponding to each token in the token stream. For example, the token `dog` becomes `god`. `shingle` | [ShingleFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/shingle/ShingleFilter.html) | Generates shingles of lengths between `min_shingle_size` and `max_shingle_size` for tokens in the token stream. Shingles are similar to n-grams but apply to words instead of letters. For example, two-word shingles added to the list of unigrams [`contribute`, `to`, `opensearch`] are [`contribute to`, `to opensearch`]. `snowball` | N/A | Stems words using a [Snowball-generated stemmer](https://snowballstem.org/). You can use the `snowball` token filter with the following languages in the `language` field: `Arabic`, `Armenian`, `Basque`, `Catalan`, `Danish`, `Dutch`, `English`, `Estonian`, `Finnish`, `French`, `German`, `German2`, `Hungarian`, `Irish`, `Italian`, `Kp`, `Lithuanian`, `Lovins`, `Norwegian`, `Porter`, `Portuguese`, `Romanian`, `Russian`, `Spanish`, `Swedish`, `Turkish`. `stemmer` | N/A | Provides algorithmic stemming for the following languages in the `language` field: `arabic`, `armenian`, `basque`, `bengali`, `brazilian`, `bulgarian`, `catalan`, `czech`, `danish`, `dutch`, `dutch_kp`, `english`, `light_english`, `lovins`, `minimal_english`, `porter2`, `possessive_english`, `estonian`, `finnish`, `light_finnish`, `french`, `light_french`, `minimal_french`, `galician`, `minimal_galician`, `german`, `german2`, `light_german`, `minimal_german`, `greek`, `hindi`, `hungarian`, `light_hungarian`, `indonesian`, `irish`, `italian`, `light_italian`, `latvian`, `Lithuanian`, `norwegian`, `light_norwegian`, `minimal_norwegian`, `light_nynorsk`, `minimal_nynorsk`, `portuguese`, `light_portuguese`, `minimal_portuguese`, `portuguese_rslp`, `romanian`, `russian`, `light_russian`, `sorani`, `spanish`, `light_spanish`, `swedish`, `light_swedish`, `turkish`. diff --git a/_analyzers/token-filters/reverse.md b/_analyzers/token-filters/reverse.md new file mode 100644 index 0000000000..dc48f07e77 --- /dev/null +++ b/_analyzers/token-filters/reverse.md @@ -0,0 +1,86 @@ +--- +layout: default +title: Reverse +parent: Token filters +nav_order: 360 +--- + +# Reverse token filter + +The `reverse` token filter reverses the order of the characters in each token, making suffix information accessible at the beginning of the reversed tokens during analysis. + +This is useful for suffix-based searches: + +The `reverse` token filter is useful when you need to perform suffix-based searches, such as in the following scenarios: + +- **Suffix matching**: Searching for words based on their suffixes, such as identifying words with a specific ending (for example, `-tion` or `-ing`). +- **File extension searches**: Searching for files by their extensions, such as `.txt` or `.jpg`. +- **Custom sorting or ranking**: By reversing tokens, you can implement unique sorting or ranking logic based on suffixes. +- **Autocomplete for suffixes**: Implementing autocomplete suggestions that use suffixes rather than prefixes. + + +## Example + +The following example request creates a new index named `my-reverse-index` and configures an analyzer with a `reverse` filter: + +```json +PUT /my-reverse-index +{ + "settings": { + "analysis": { + "filter": { + "reverse_filter": { + "type": "reverse" + } + }, + "analyzer": { + "my_reverse_analyzer": { + "type": "custom", + "tokenizer": "standard", + "filter": [ + "lowercase", + "reverse_filter" + ] + } + } + } + } +} +``` +{% include copy-curl.html %} + +## Generated tokens + +Use the following request to examine the tokens generated using the analyzer: + +```json +GET /my-reverse-index/_analyze +{ + "analyzer": "my_reverse_analyzer", + "text": "hello world" +} +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + { + "token": "olleh", + "start_offset": 0, + "end_offset": 5, + "type": "", + "position": 0 + }, + { + "token": "dlrow", + "start_offset": 6, + "end_offset": 11, + "type": "", + "position": 1 + } + ] +} +``` \ No newline at end of file From 335c25ce13f0a3f83ee22d3e4042cec867ec256d Mon Sep 17 00:00:00 2001 From: 10000-ki <10000ki6472@gmail.com> Date: Tue, 3 Dec 2024 01:37:53 +0900 Subject: [PATCH 07/69] Fix typos related to the ML connector (#8832) Signed-off-by: 10000-ki <10000ki6472@gmail.com> Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --- _ml-commons-plugin/remote-models/connectors.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_ml-commons-plugin/remote-models/connectors.md b/_ml-commons-plugin/remote-models/connectors.md index 3ec6c73b07..788f1b003d 100644 --- a/_ml-commons-plugin/remote-models/connectors.md +++ b/_ml-commons-plugin/remote-models/connectors.md @@ -294,7 +294,7 @@ In some cases, you may need to update credentials, like `access_key`, that you u ```json PUT /_plugins/_ml/models/ { - "connector": { + "connectors": { "credential": { "openAI_key": "YOUR NEW OPENAI KEY" } From e019d964e0723388a1e8c5d572833dfb5a5bd456 Mon Sep 17 00:00:00 2001 From: AntonEliatra Date: Mon, 2 Dec 2024 16:58:22 +0000 Subject: [PATCH 08/69] Add shingle token filter docs (#8398) * adding shingle token filter docs Signed-off-by: Anton Rubin * updating parameter table Signed-off-by: Anton Rubin * Doc review Signed-off-by: Fanit Kolchina * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --------- Signed-off-by: Anton Rubin Signed-off-by: Fanit Kolchina Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Fanit Kolchina Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Nathan Bower --- _analyzers/token-filters/index.md | 2 +- _analyzers/token-filters/shingle.md | 120 ++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 _analyzers/token-filters/shingle.md diff --git a/_analyzers/token-filters/index.md b/_analyzers/token-filters/index.md index 961be36f85..d363eedf33 100644 --- a/_analyzers/token-filters/index.md +++ b/_analyzers/token-filters/index.md @@ -52,7 +52,7 @@ Token filter | Underlying Lucene token filter| Description [`predicate_token_filter`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/predicate-token-filter/) | N/A | Removes tokens that do not match the specified predicate script. Supports only inline Painless scripts. [`remove_duplicates`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/remove-duplicates/) | [RemoveDuplicatesTokenFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/RemoveDuplicatesTokenFilter.html) | Removes duplicate tokens that are in the same position. [`reverse`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/reverse/) | [ReverseStringFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/reverse/ReverseStringFilter.html) | Reverses the string corresponding to each token in the token stream. For example, the token `dog` becomes `god`. -`shingle` | [ShingleFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/shingle/ShingleFilter.html) | Generates shingles of lengths between `min_shingle_size` and `max_shingle_size` for tokens in the token stream. Shingles are similar to n-grams but apply to words instead of letters. For example, two-word shingles added to the list of unigrams [`contribute`, `to`, `opensearch`] are [`contribute to`, `to opensearch`]. +[`shingle`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/shingle/) | [ShingleFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/shingle/ShingleFilter.html) | Generates shingles of lengths between `min_shingle_size` and `max_shingle_size` for tokens in the token stream. Shingles are similar to n-grams but are generated using words instead of letters. For example, two-word shingles added to the list of unigrams [`contribute`, `to`, `opensearch`] are [`contribute to`, `to opensearch`]. `snowball` | N/A | Stems words using a [Snowball-generated stemmer](https://snowballstem.org/). You can use the `snowball` token filter with the following languages in the `language` field: `Arabic`, `Armenian`, `Basque`, `Catalan`, `Danish`, `Dutch`, `English`, `Estonian`, `Finnish`, `French`, `German`, `German2`, `Hungarian`, `Irish`, `Italian`, `Kp`, `Lithuanian`, `Lovins`, `Norwegian`, `Porter`, `Portuguese`, `Romanian`, `Russian`, `Spanish`, `Swedish`, `Turkish`. `stemmer` | N/A | Provides algorithmic stemming for the following languages in the `language` field: `arabic`, `armenian`, `basque`, `bengali`, `brazilian`, `bulgarian`, `catalan`, `czech`, `danish`, `dutch`, `dutch_kp`, `english`, `light_english`, `lovins`, `minimal_english`, `porter2`, `possessive_english`, `estonian`, `finnish`, `light_finnish`, `french`, `light_french`, `minimal_french`, `galician`, `minimal_galician`, `german`, `german2`, `light_german`, `minimal_german`, `greek`, `hindi`, `hungarian`, `light_hungarian`, `indonesian`, `irish`, `italian`, `light_italian`, `latvian`, `Lithuanian`, `norwegian`, `light_norwegian`, `minimal_norwegian`, `light_nynorsk`, `minimal_nynorsk`, `portuguese`, `light_portuguese`, `minimal_portuguese`, `portuguese_rslp`, `romanian`, `russian`, `light_russian`, `sorani`, `spanish`, `light_spanish`, `swedish`, `light_swedish`, `turkish`. `stemmer_override` | N/A | Overrides stemming algorithms by applying a custom mapping so that the provided terms are not stemmed. diff --git a/_analyzers/token-filters/shingle.md b/_analyzers/token-filters/shingle.md new file mode 100644 index 0000000000..ea961bf3e0 --- /dev/null +++ b/_analyzers/token-filters/shingle.md @@ -0,0 +1,120 @@ +--- +layout: default +title: Shingle +parent: Token filters +nav_order: 370 +--- + +# Shingle token filter + +The `shingle` token filter is used to generate word n-grams, or _shingles_, from input text. For example, for the string `slow green turtle`, the `shingle` filter creates the following one- and two-word shingles: `slow`, `slow green`, `green`, `green turtle`, and `turtle`. + +This token filter is often used in conjunction with other filters to enhance search accuracy by indexing phrases rather than individual tokens. For more information, see [Phrase suggester]({{site.url}}{{site.baseurl}}/search-plugins/searching-data/did-you-mean/#phrase-suggester). + +## Parameters + +The `shingle` token filter can be configured with the following parameters. + +Parameter | Required/Optional | Data type | Description +:--- | :--- | :--- | :--- +`min_shingle_size` | Optional | Integer | The minimum number of tokens to concatenate. Default is `2`. +`max_shingle_size` | Optional | Integer | The maximum number of tokens to concatenate. Default is `2`. +`output_unigrams` | Optional | Boolean | Whether to include unigrams (individual tokens) as output. Default is `true`. +`output_unigrams_if_no_shingles` | Optional | Boolean | Whether to output unigrams if no shingles are generated. Default is `false`. +`token_separator` | Optional | String | A separator used to concatenate tokens into a shingle. Default is a space (`" "`). +`filler_token` | Optional | String | A token inserted into empty positions or gaps between tokens. Default is an underscore (`_`). + +If `output_unigrams` and `output_unigrams_if_no_shingles` are both set to `true`, `output_unigrams_if_no_shingles` is ignored. +{: .note} + +## Example + +The following example request creates a new index named `my-shingle-index` and configures an analyzer with a `shingle` filter: + +```json +PUT /my-shingle-index +{ + "settings": { + "analysis": { + "filter": { + "my_shingle_filter": { + "type": "shingle", + "min_shingle_size": 2, + "max_shingle_size": 2, + "output_unigrams": true + } + }, + "analyzer": { + "my_shingle_analyzer": { + "type": "custom", + "tokenizer": "standard", + "filter": [ + "lowercase", + "my_shingle_filter" + ] + } + } + } + } +} +``` +{% include copy-curl.html %} + +## Generated tokens + +Use the following request to examine the tokens generated using the analyzer: + +```json +GET /my-shingle-index/_analyze +{ + "analyzer": "my_shingle_analyzer", + "text": "slow green turtle" +} +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + { + "token": "slow", + "start_offset": 0, + "end_offset": 4, + "type": "", + "position": 0 + }, + { + "token": "slow green", + "start_offset": 0, + "end_offset": 10, + "type": "shingle", + "position": 0, + "positionLength": 2 + }, + { + "token": "green", + "start_offset": 5, + "end_offset": 10, + "type": "", + "position": 1 + }, + { + "token": "green turtle", + "start_offset": 5, + "end_offset": 17, + "type": "shingle", + "position": 1, + "positionLength": 2 + }, + { + "token": "turtle", + "start_offset": 11, + "end_offset": 17, + "type": "", + "position": 2 + } + ] +} +``` \ No newline at end of file From 6acaba13f6178fb7dbcb7548442419dec2bb80b7 Mon Sep 17 00:00:00 2001 From: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> Date: Mon, 2 Dec 2024 11:06:44 -0600 Subject: [PATCH 09/69] Add instructions for deploying migration assistant. (#8798) * Add instructions for deploying migration assistant. Signed-off-by: Archer * Apply suggestions from code review Co-authored-by: Peter Nied Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Fix typos Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Update _migrations/deploying-migration-assistant/configuration-options.md Co-authored-by: Nathan Bower Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> --------- Signed-off-by: Archer Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> Co-authored-by: Peter Nied Co-authored-by: Nathan Bower --- .../configuration-options.md | 154 +++++------------- ...d-security-groups-for-existing-clusters.md | 24 +-- .../deploying-migration-assistant/index.md | 8 +- 3 files changed, 64 insertions(+), 122 deletions(-) diff --git a/_migrations/deploying-migration-assistant/configuration-options.md b/_migrations/deploying-migration-assistant/configuration-options.md index 5a77bd518a..2e7f43e1b5 100644 --- a/_migrations/deploying-migration-assistant/configuration-options.md +++ b/_migrations/deploying-migration-assistant/configuration-options.md @@ -7,66 +7,35 @@ parent: Deploying migration assistant # Configuration options -This page outlines the configuration options for three key migrations: -1. **Metadata Migration** -2. **Backfill Migration with Reindex-from-Snapshot (RFS)** -3. **Live Capture Migration with Capture and Replay (C&R)** +This page outlines the configuration options for three key migrations scenarios: -Each of these migrations may depend on either a snapshot or a capture proxy. The CDK context blocks below are shown as separate context blocks for each migration type for simplicity. If performing multiple migration types, combine these options, as the actual execution of each migration is controlled from the Migration Console. +1. **Metadata migration** +2. **Backfill migration with `Reindex-from-Snapshot` (RFS)** +3. **Live capture migration with Capture and Replay (C&R)** -It also has a section describing how to specify the auth details for the source and target cluster (no auth, basic auth with a username and password, or sigv4 auth). +Each of these migrations depends on either a snapshot or a capture proxy. The following example `cdk.context.json` configurations are used by AWS Cloud Development Kit (AWS CDK) to deploy and configure Migration Assistant for OpenSearch, shown as separate blocks for each migration type. If you are performing a migration applicable to multiple scenarios, these options can be combined. -> [!TIP] -For a complete list of configuration options, please refer to the [opensearch-migrations options.md](https://github.com/opensearch-project/opensearch-migrations/blob/main/deployment/cdk/opensearch-service-migration/options.md) but please open an issue for consultation if changing an option that is not listed on this page. -Options for the source cluster endpoint, target cluster endpoint, and existing VPC should be configured for the Migration tools to function effectively. +For a complete list of configuration options, see [opensearch-migrations-options.md](https://github.com/opensearch-project/opensearch-migrations/blob/main/deployment/cdk/opensearch-service-migration/options.md). If you need a configuration option that is not found on this page, create an issue in the [OpenSearch Migrations repository](https://github.com/opensearch-project/opensearch-migrations/issues). +{: .tip } +Options for the source cluster endpoint, target cluster endpoint, and existing virtual private cloud (VPC) should be configured in order for the migration tools to function effectively. -## Metadata Migration Options +## Shared configuration options -## Sample Metadata Migration CDK Options +Each migration configuration shares the following options. -```json -{ - "metadata-migration": { - "stage": "dev", - "vpcId": , - "sourceCluster": { - "endpoint": , - "version": "ES 7.10", - "auth": {"type": "none"} - }, - "targetCluster": { - "endpoint": , - "auth": { - "type": "basic", - "username": , - "passwordFromSecretArn": - } - }, - "reindexFromSnapshotServiceEnabled": true, - "artifactBucketRemovalPolicy": "DESTROY" - } -} -``` - -There are currently no CDK options specific to Metadata migrations, which are performed from the Migration Console. This migration requires an existing snapshot, which can be created from the Migration Console. -
-Shared configuration options table - +| Name | Example | Description | +| :--- | :--- | :--- | +| `sourceClusterEndpoint` | `"https://source-cluster.elb.us-east-1.endpoint.com"` | The endpoint for the source cluster. | +| `targetClusterEndpoint` | `"https://vpc-demo-opensearch-cluster-cv6hggdb66ybpk4kxssqt6zdhu.us-west-2.es.amazonaws.com:443"` | The endpoint for the target cluster. Required if using an existing target cluster for the migration instead of creating a new one. | +| `vpcId` | `"vpc-123456789abcdefgh"` | The ID of the existing VPC in which the migration resources will be stored. The VPC must have at least two private subnets that span two Availability Zones. | -| Name | Example | Description | -|-----------------------|-----------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `sourceClusterEndpoint` | `"https://source-cluster.elb.us-east-1.endpoint.com"` | The endpoint for the source cluster. | -| `targetClusterEndpoint` | `"https://vpc-demo-opensearch-cluster-cv6hggdb66ybpk4kxssqt6zdhu.us-west-2.es.amazonaws.com:443"` | The endpoint for the target cluster. Required if using an existing target cluster for the migration instead of creating a new one. | -| `vpcId` | `"vpc-123456789abcdefgh"` | The ID of the existing VPC where the migration resources will be placed. The VPC must have at least two private subnets that span two availability zones. | -
+## Backfill migration using RFS -## Backfill Migration with Reindex-from-Snapshot (RFS) Options - -### Sample Backfill Migration CDK Options +The following CDK performs a backfill migrations using RFS: ```json { @@ -93,22 +62,21 @@ There are currently no CDK options specific to Metadata migrations, which are pe } ``` -Performing a Reindex-from-Snapshot backfill migration requires an existing snapshot. The CDK options specific to backfill migrations are listed below. To view all available arguments for `reindexFromSnapshotExtraArgs`, see [here](https://github.com/opensearch-project/opensearch-migrations/blob/main/DocumentsFromSnapshotMigration/README.md#arguments). At a minimum, no extra arguments may be needed. +Performing an RFS backfill migration requires an existing snapshot. + -
-Backfill specific configuration options table - +The RFS configuration uses the following options. All options are optional. -| Name | Example | Description | -|---------------------------------|-----------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `reindexFromSnapshotServiceEnabled` | `true` | Enables deploying and configuring the RFS ECS service. | -| `reindexFromSnapshotExtraArgs` | `"--target-aws-region us-east-1 --target-aws-service-signing-name es"` | Extra arguments for the Document Migration command, with space separation. See the [RFS Extra Arguments](https://github.com/opensearch-project/opensearch-migrations/blob/main/DocumentsFromSnapshotMigration/README.md#arguments) for more details. You can pass `--no-insecure` to remove the `--insecure` flag. | +| Name | Example | Description | +| :--- | :--- | :--- | +| `reindexFromSnapshotServiceEnabled` | `true` | Enables deployment and configuration of the RFS ECS service. | +| `reindexFromSnapshotExtraArgs` | `"--target-aws-region us-east-1 --target-aws-service-signing-name es"` | Extra arguments for the Document Migration command, with space separation. See [RFS Extra Arguments](https://github.com/opensearch-project/opensearch-migrations/blob/main/DocumentsFromSnapshotMigration/README.md#arguments) for more information. You can pass `--no-insecure` to remove the `--insecure` flag. | -
+To view all available arguments for `reindexFromSnapshotExtraArgs`, see [Snapshot migrations README](https://github.com/opensearch-project/opensearch-migrations/blob/main/DocumentsFromSnapshotMigration/README.md#arguments). At a minimum, no extra arguments may be needed. -## Live Capture Migration with Capture and Replay (C&R) Options +## Live capture migration with C&R -### Sample Live Capture Migration CDK Options +The following sample CDK performs a live capture migration with C&R: ```json { @@ -137,28 +105,26 @@ Performing a Reindex-from-Snapshot backfill migration requires an existing snaps } ``` -Performing a live capture migration requires that a Capture Proxy be configured to capture incoming traffic and send it to the target cluster via the Traffic Replayer service. For arguments available in `captureProxyExtraArgs`, refer to the `@Parameter` fields [here](https://github.com/opensearch-project/opensearch-migrations/blob/main/TrafficCapture/trafficCaptureProxyServer/src/main/java/org/opensearch/migrations/trafficcapture/proxyserver/CaptureProxy.java). For `trafficReplayerExtraArgs`, refer to the `@Parameter` fields [here](https://github.com/opensearch-project/opensearch-migrations/blob/main/TrafficCapture/trafficReplayer/src/main/java/org/opensearch/migrations/replay/TrafficReplayer.java). At a minimum, no extra arguments may be needed. +Performing a live capture migration requires that a Capture Proxy be configured to capture incoming traffic and send it to the target cluster using the Traffic Replayer service. For arguments available in `captureProxyExtraArgs`, refer to the `@Parameter` fields [here](https://github.com/opensearch-project/opensearch-migrations/blob/main/TrafficCapture/trafficCaptureProxyServer/src/main/java/org/opensearch/migrations/trafficcapture/proxyserver/CaptureProxy.java). For `trafficReplayerExtraArgs`, refer to the `@Parameter` fields [here](https://github.com/opensearch-project/opensearch-migrations/blob/main/TrafficCapture/trafficReplayer/src/main/java/org/opensearch/migrations/replay/TrafficReplayer.java). At a minimum, no extra arguments may be needed. -
-Capture and Replay specific configuration options table - -| Name | Example | Description | -|--------------------------------|----------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `captureProxyServiceEnabled` | `true` | Enables the Capture Proxy service deployment via a new CloudFormation stack. | -| `captureProxyExtraArgs` | `"--suppressCaptureForHeaderMatch user-agent .*elastic-java/7.17.0.*"` | Extra arguments for the Capture Proxy command, including options specified by the [Capture Proxy](https://github.com/opensearch-project/opensearch-migrations/blob/main/TrafficCapture/trafficCaptureProxyServer/src/main/java/org/opensearch/migrations/trafficcapture/proxyserver/CaptureProxy.java). | -| `trafficReplayerServiceEnabled` | `true` | Enables the Traffic Replayer service deployment via a new CloudFormation stack. | +| Name | Example | Description | +| :--- | :--- | :--- | +| `captureProxyServiceEnabled` | `true` | Enables the Capture Proxy service deployment using an AWS CloudFormation stack. | +| `captureProxyExtraArgs` | `"--suppressCaptureForHeaderMatch user-agent .*elastic-java/7.17.0.*"` | Extra arguments for the Capture Proxy command, including options specified by the [Capture Proxy](https://github.com/opensearch-project/opensearch-migrations/blob/main/TrafficCapture/trafficCaptureProxyServer/src/main/java/org/opensearch/migrations/trafficcapture/proxyserver/CaptureProxy.java). | +| `trafficReplayerServiceEnabled` | `true` | Enables the Traffic Replayer service deployment using a CloudFormation stack. | | `trafficReplayerExtraArgs` | `"--sigv4-auth-header-service-region es,us-east-1 --speedup-factor 5"` | Extra arguments for the Traffic Replayer command, including options for auth headers and other parameters specified by the [Traffic Replayer](https://github.com/opensearch-project/opensearch-migrations/blob/main/TrafficCapture/trafficReplayer/src/main/java/org/opensearch/migrations/replay/TrafficReplayer.java). | -
-## Cluster Authentication Options +For arguments available in `captureProxyExtraArgs`, see the `@Parameter` fields in [`CaptureProxy.java`](https://github.com/opensearch-project/opensearch-migrations/blob/main/TrafficCapture/trafficCaptureProxyServer/src/main/java/org/opensearch/migrations/trafficcapture/proxyserver/CaptureProxy.java). For `trafficReplayerExtraArgs`, see the `@Parameter` fields in [TrafficReplayer.java](https://github.com/opensearch-project/opensearch-migrations/blob/main/TrafficCapture/trafficReplayer/src/main/java/org/opensearch/migrations/replay/TrafficReplayer.java). + + +## Cluster authentication options -Both the source and target cluster can use no authentication (e.g. limited to the VPC), basic authentication with a username and password, or SigV4 scoped to a user or role. +Both the source and target cluster can use no authentication, authentication limited to VPC, basic authentication with a username and password, or AWS Signature Version 4 scoped to a user or role. -Examples of each of these are below. +### No authentication -No auth: ``` "sourceCluster": { "endpoint": , @@ -167,7 +133,8 @@ No auth: } ``` -Basic auth: +### Basic authentication + ``` "sourceCluster": { "endpoint": , @@ -180,7 +147,8 @@ Basic auth: } ``` -SigV4 auth: +### Signature Version 4 authentication + ``` "sourceCluster": { "endpoint": , @@ -195,40 +163,8 @@ SigV4 auth: The `serviceSigningName` can be `es` for an Elasticsearch or OpenSearch domain, or `aoss` for an OpenSearch Serverless collection. -All of these auth mechanisms apply to both source and target clusters. - -## Troubleshooting - -### Restricted Permissions -When deploying if part of an [AWS Organization](https://docs.aws.amazon.com/organizations/latest/userguide/orgs_introduction.html) ↗ some permissions / resources might not be allowed. The full list can be generated from the synthesized cdk output with the awsFeatureUsage script. - -``` -/opensearch-migrations/deployment/cdk/opensearch-service-migration/awsFeatureUsage.sh [contextId] -``` - -
-Capture and Replay specific configuration options table - - -```shell -$ /opensearch-migrations/deployment/cdk/opensearch-service-migration/awsFeatureUsage.sh default -Synthesizing all stacks... -Synthesizing stack: networkStack-default -Synthesizing stack: migrationInfraStack -Synthesizing stack: reindexFromSnapshotStack -Synthesizing stack: migration-console -Finding resource usage from synthesized stacks... ------------------------------------ -IAM Policy Actions: -cloudwatch:GetMetricData -... ------------------------------------ -Resources Types: -AWS::CDK::Metadata -... -``` -
+All of these authentication options apply to both source and target clusters. +## Network configuration -### Network Configuration The migration tooling expects the source cluster, target cluster, and migration resources to exist in the same VPC. If this is not the case, manual networking setup outside of this documentation is likely required. diff --git a/_migrations/deploying-migration-assistant/iam-and-security-groups-for-existing-clusters.md b/_migrations/deploying-migration-assistant/iam-and-security-groups-for-existing-clusters.md index 46f1d7e11e..808de79689 100644 --- a/_migrations/deploying-migration-assistant/iam-and-security-groups-for-existing-clusters.md +++ b/_migrations/deploying-migration-assistant/iam-and-security-groups-for-existing-clusters.md @@ -7,25 +7,27 @@ parent: Deploying migration assistant # IAM and security groups for existing clusters -This page outlines scenarios for using the migration tools with existing clusters, including any necessary configuration changes to ensure proper communication between them. +This page outlines security scenarios for using the migration tools with existing clusters, including any necessary configuration changes to ensure proper communication between them. -## Importing an OpenSearch Service or OpenSearch Serverless Target Cluster +## Importing an Amazon OpenSearch Service or Amazon OpenSearch Serverless target cluster + +Use the following scenarios for Amazon OpenSearch Service or Amazon OpenSearch Serverless target clusters. ### OpenSearch Service For an OpenSearch Domain, two main configurations are typically required to ensure proper functioning of the migration solution: -1. **Security Group Configuration**: - The Domain should have a security group that allows communication from the applicable Migration services (Traffic Replayer, Migration Console, Reindex-from-Snapshot). The CDK will automatically create an `osClusterAccessSG` security group, which is applied to the Migration services. The user should then add this security group to their existing Domain to allow access. +1. **Security Group Configuration** + + The domain should have a security group that allows communication from the applicable migration services (Traffic Replayer, Migration Console, `Reindex-from-Snapshot`). The CDK automatically creates an `osClusterAccessSG` security group, which is applied to the migration services. The user should then add this security group to their existing domain to allow access. -2. **Access Policy Configuration**: - The Domain’s access policy should either: - - Be an open access policy that allows all access, or - - Be configured to allow at least the IAM task roles for the applicable Migration services (Traffic Replayer, Migration Console, Reindex-from-Snapshot) to access the Domain. +2. **Access Policy Configuration** should be one of the following: + - An open access policy that allows all access. + - Configured to allow at least the AWS Identity and Access Management (IAM) task roles for the applicable migration services (Traffic Replayer, Migration Console, `Reindex-from-Snapshot`) to access the domain. ### OpenSearch Serverless -For an OpenSearch Serverless Collection, you will need to configure both Network and Data Access policies: +For an OpenSearch Serverless Collection, you will need to configure both network and data access policies: 1. **Network Policy Configuration**: The Collection should have a network policy that uses the `VPC` access type. This requires creating a VPC endpoint on the VPC used for the solution. The VPC endpoint should be configured for the private subnets of the VPC and should attach the `osClusterAccessSG` security group. @@ -35,7 +37,7 @@ For an OpenSearch Serverless Collection, you will need to configure both Network ## Capture Proxy on Coordinator Nodes of Source Cluster -Although the CDK does not automatically set up the Capture Proxy on source cluster nodes (except in the demo solution), the Capture Proxy instances must communicate with the resources deployed by the CDK (e.g., Kafka). This section outlines the necessary steps. +Although the CDK does not automatically set up the Capture Proxy on source cluster nodes (except in the demo solution), the Capture Proxy instances must communicate with the resources deployed by the CDK, such as Kafka. This section outlines the necessary steps to set up communication. Before [setting up Capture Proxy instances](https://github.com/opensearch-project/opensearch-migrations/tree/main/TrafficCapture/trafficCaptureProxyServer#installing-capture-proxy-on-coordinator-nodes) on the source cluster, ensure the following configurations are in place: @@ -69,4 +71,4 @@ Before [setting up Capture Proxy instances](https://github.com/opensearch-projec ## Related Links -- [OpenSearch Traffic Capture Setup](https://github.com/opensearch-project/opensearch-migrations/tree/main/TrafficCapture/trafficCaptureProxyServer#installing-capture-proxy-on-coordinator-nodes) ↗ \ No newline at end of file +- [OpenSearch traffic capture setup] \ No newline at end of file diff --git a/_migrations/deploying-migration-assistant/index.md b/_migrations/deploying-migration-assistant/index.md index cbe721dd12..6e245aa5da 100644 --- a/_migrations/deploying-migration-assistant/index.md +++ b/_migrations/deploying-migration-assistant/index.md @@ -1,5 +1,9 @@ --- layout: default -title: Deploying migration assistant +title: Deploying Migration Assistant nav_order: 10 ---- \ No newline at end of file +--- + +# Deploying Migration Assistant + +This section provides information about the available options for deploying Migration Assistant. From 6ff64816f10143d62146631131b85076ea080ec0 Mon Sep 17 00:00:00 2001 From: AntonEliatra Date: Mon, 2 Dec 2024 17:14:33 +0000 Subject: [PATCH 10/69] Add snowball token filter docs #8276 (#8399) * adding snowball token filter docs #8276 Signed-off-by: Anton Rubin * Doc review Signed-off-by: Fanit Kolchina * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --------- Signed-off-by: Anton Rubin Signed-off-by: Fanit Kolchina Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Fanit Kolchina Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Nathan Bower --- _analyzers/token-filters/index.md | 2 +- _analyzers/token-filters/snowball.md | 108 +++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 _analyzers/token-filters/snowball.md diff --git a/_analyzers/token-filters/index.md b/_analyzers/token-filters/index.md index d363eedf33..b3416ee0ff 100644 --- a/_analyzers/token-filters/index.md +++ b/_analyzers/token-filters/index.md @@ -53,7 +53,7 @@ Token filter | Underlying Lucene token filter| Description [`remove_duplicates`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/remove-duplicates/) | [RemoveDuplicatesTokenFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/RemoveDuplicatesTokenFilter.html) | Removes duplicate tokens that are in the same position. [`reverse`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/reverse/) | [ReverseStringFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/reverse/ReverseStringFilter.html) | Reverses the string corresponding to each token in the token stream. For example, the token `dog` becomes `god`. [`shingle`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/shingle/) | [ShingleFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/shingle/ShingleFilter.html) | Generates shingles of lengths between `min_shingle_size` and `max_shingle_size` for tokens in the token stream. Shingles are similar to n-grams but are generated using words instead of letters. For example, two-word shingles added to the list of unigrams [`contribute`, `to`, `opensearch`] are [`contribute to`, `to opensearch`]. -`snowball` | N/A | Stems words using a [Snowball-generated stemmer](https://snowballstem.org/). You can use the `snowball` token filter with the following languages in the `language` field: `Arabic`, `Armenian`, `Basque`, `Catalan`, `Danish`, `Dutch`, `English`, `Estonian`, `Finnish`, `French`, `German`, `German2`, `Hungarian`, `Irish`, `Italian`, `Kp`, `Lithuanian`, `Lovins`, `Norwegian`, `Porter`, `Portuguese`, `Romanian`, `Russian`, `Spanish`, `Swedish`, `Turkish`. +[`snowball`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/snowball/) | N/A | Stems words using a [Snowball-generated stemmer](https://snowballstem.org/). The `snowball` token filter supports using the following languages in the `language` field: `Arabic`, `Armenian`, `Basque`, `Catalan`, `Danish`, `Dutch`, `English`, `Estonian`, `Finnish`, `French`, `German`, `German2`, `Hungarian`, `Irish`, `Italian`, `Kp`, `Lithuanian`, `Lovins`, `Norwegian`, `Porter`, `Portuguese`, `Romanian`, `Russian`, `Spanish`, `Swedish`, `Turkish`. `stemmer` | N/A | Provides algorithmic stemming for the following languages in the `language` field: `arabic`, `armenian`, `basque`, `bengali`, `brazilian`, `bulgarian`, `catalan`, `czech`, `danish`, `dutch`, `dutch_kp`, `english`, `light_english`, `lovins`, `minimal_english`, `porter2`, `possessive_english`, `estonian`, `finnish`, `light_finnish`, `french`, `light_french`, `minimal_french`, `galician`, `minimal_galician`, `german`, `german2`, `light_german`, `minimal_german`, `greek`, `hindi`, `hungarian`, `light_hungarian`, `indonesian`, `irish`, `italian`, `light_italian`, `latvian`, `Lithuanian`, `norwegian`, `light_norwegian`, `minimal_norwegian`, `light_nynorsk`, `minimal_nynorsk`, `portuguese`, `light_portuguese`, `minimal_portuguese`, `portuguese_rslp`, `romanian`, `russian`, `light_russian`, `sorani`, `spanish`, `light_spanish`, `swedish`, `light_swedish`, `turkish`. `stemmer_override` | N/A | Overrides stemming algorithms by applying a custom mapping so that the provided terms are not stemmed. `stop` | [StopFilter](https://lucene.apache.org/core/8_7_0/core/org/apache/lucene/analysis/StopFilter.html) | Removes stop words from a token stream. diff --git a/_analyzers/token-filters/snowball.md b/_analyzers/token-filters/snowball.md new file mode 100644 index 0000000000..149486e727 --- /dev/null +++ b/_analyzers/token-filters/snowball.md @@ -0,0 +1,108 @@ +--- +layout: default +title: Snowball +parent: Token filters +nav_order: 380 +--- + +# Snowball token filter + +The `snowball` token filter is a stemming filter based on the [Snowball](https://snowballstem.org/) algorithm. It supports many languages and is more efficient and accurate than the Porter stemming algorithm. + +## Parameters + +The `snowball` token filter can be configured with a `language` parameter that accepts the following values: + +- `Arabic` +- `Armenian` +- `Basque` +- `Catalan` +- `Danish` +- `Dutch` +- `English` (default) +- `Estonian` +- `Finnish` +- `French` +- `German` +- `German2` +- `Hungarian` +- `Italian` +- `Irish` +- `Kp` +- `Lithuanian` +- `Lovins` +- `Norwegian` +- `Porter` +- `Portuguese` +- `Romanian` +- `Russian` +- `Spanish` +- `Swedish` +- `Turkish` + +## Example + +The following example request creates a new index named `my-snowball-index` and configures an analyzer with a `snowball` filter: + +```json +PUT /my-snowball-index +{ + "settings": { + "analysis": { + "filter": { + "my_snowball_filter": { + "type": "snowball", + "language": "English" + } + }, + "analyzer": { + "my_snowball_analyzer": { + "type": "custom", + "tokenizer": "standard", + "filter": [ + "lowercase", + "my_snowball_filter" + ] + } + } + } + } +} +``` +{% include copy-curl.html %} + +## Generated tokens + +Use the following request to examine the tokens generated using the analyzer: + +```json +GET /my-snowball-index/_analyze +{ + "analyzer": "my_snowball_analyzer", + "text": "running runners" +} +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + { + "token": "run", + "start_offset": 0, + "end_offset": 7, + "type": "", + "position": 0 + }, + { + "token": "runner", + "start_offset": 8, + "end_offset": 15, + "type": "", + "position": 1 + } + ] +} +``` \ No newline at end of file From 74ade40c76367ebabcdec177393f03e4b3a766ff Mon Sep 17 00:00:00 2001 From: AntonEliatra Date: Mon, 2 Dec 2024 17:19:26 +0000 Subject: [PATCH 11/69] add stemmer token filter docs #8277 (#8444) * add stemmer token filter docs #8277 Signed-off-by: Anton Rubin * Doc review Signed-off-by: Fanit Kolchina * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --------- Signed-off-by: Anton Rubin Signed-off-by: Fanit Kolchina Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Fanit Kolchina Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Nathan Bower --- _analyzers/token-filters/index.md | 2 +- _analyzers/token-filters/stemmer.md | 118 ++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 _analyzers/token-filters/stemmer.md diff --git a/_analyzers/token-filters/index.md b/_analyzers/token-filters/index.md index b3416ee0ff..f61c7db6e3 100644 --- a/_analyzers/token-filters/index.md +++ b/_analyzers/token-filters/index.md @@ -54,7 +54,7 @@ Token filter | Underlying Lucene token filter| Description [`reverse`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/reverse/) | [ReverseStringFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/reverse/ReverseStringFilter.html) | Reverses the string corresponding to each token in the token stream. For example, the token `dog` becomes `god`. [`shingle`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/shingle/) | [ShingleFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/shingle/ShingleFilter.html) | Generates shingles of lengths between `min_shingle_size` and `max_shingle_size` for tokens in the token stream. Shingles are similar to n-grams but are generated using words instead of letters. For example, two-word shingles added to the list of unigrams [`contribute`, `to`, `opensearch`] are [`contribute to`, `to opensearch`]. [`snowball`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/snowball/) | N/A | Stems words using a [Snowball-generated stemmer](https://snowballstem.org/). The `snowball` token filter supports using the following languages in the `language` field: `Arabic`, `Armenian`, `Basque`, `Catalan`, `Danish`, `Dutch`, `English`, `Estonian`, `Finnish`, `French`, `German`, `German2`, `Hungarian`, `Irish`, `Italian`, `Kp`, `Lithuanian`, `Lovins`, `Norwegian`, `Porter`, `Portuguese`, `Romanian`, `Russian`, `Spanish`, `Swedish`, `Turkish`. -`stemmer` | N/A | Provides algorithmic stemming for the following languages in the `language` field: `arabic`, `armenian`, `basque`, `bengali`, `brazilian`, `bulgarian`, `catalan`, `czech`, `danish`, `dutch`, `dutch_kp`, `english`, `light_english`, `lovins`, `minimal_english`, `porter2`, `possessive_english`, `estonian`, `finnish`, `light_finnish`, `french`, `light_french`, `minimal_french`, `galician`, `minimal_galician`, `german`, `german2`, `light_german`, `minimal_german`, `greek`, `hindi`, `hungarian`, `light_hungarian`, `indonesian`, `irish`, `italian`, `light_italian`, `latvian`, `Lithuanian`, `norwegian`, `light_norwegian`, `minimal_norwegian`, `light_nynorsk`, `minimal_nynorsk`, `portuguese`, `light_portuguese`, `minimal_portuguese`, `portuguese_rslp`, `romanian`, `russian`, `light_russian`, `sorani`, `spanish`, `light_spanish`, `swedish`, `light_swedish`, `turkish`. +[`stemmer`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/stemmer/) | N/A | Provides algorithmic stemming for the following languages used in the `language` field: `arabic`, `armenian`, `basque`, `bengali`, `brazilian`, `bulgarian`, `catalan`, `czech`, `danish`, `dutch`, `dutch_kp`, `english`, `light_english`, `lovins`, `minimal_english`, `porter2`, `possessive_english`, `estonian`, `finnish`, `light_finnish`, `french`, `light_french`, `minimal_french`, `galician`, `minimal_galician`, `german`, `german2`, `light_german`, `minimal_german`, `greek`, `hindi`, `hungarian`, `light_hungarian`, `indonesian`, `irish`, `italian`, `light_italian`, `latvian`, `Lithuanian`, `norwegian`, `light_norwegian`, `minimal_norwegian`, `light_nynorsk`, `minimal_nynorsk`, `portuguese`, `light_portuguese`, `minimal_portuguese`, `portuguese_rslp`, `romanian`, `russian`, `light_russian`, `sorani`, `spanish`, `light_spanish`, `swedish`, `light_swedish`, `turkish`. `stemmer_override` | N/A | Overrides stemming algorithms by applying a custom mapping so that the provided terms are not stemmed. `stop` | [StopFilter](https://lucene.apache.org/core/8_7_0/core/org/apache/lucene/analysis/StopFilter.html) | Removes stop words from a token stream. [`synonym`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/synonym/) | N/A | Supplies a synonym list for the analysis process. The synonym list is provided using a configuration file. diff --git a/_analyzers/token-filters/stemmer.md b/_analyzers/token-filters/stemmer.md new file mode 100644 index 0000000000..dd1344fcbc --- /dev/null +++ b/_analyzers/token-filters/stemmer.md @@ -0,0 +1,118 @@ +--- +layout: default +title: Stemmer +parent: Token filters +nav_order: 390 +--- + +# Stemmer token filter + +The `stemmer` token filter reduces words to their root or base form (also known as their _stem_). + +## Parameters + +The `stemmer` token filter can be configured with a `language` parameter that accepts the following values: + +- Arabic: `arabic` +- Armenian: `armenian` +- Basque: `basque` +- Bengali: `bengali` +- Brazilian Portuguese: `brazilian` +- Bulgarian: `bulgarian` +- Catalan: `catalan` +- Czech: `czech` +- Danish: `danish` +- Dutch: `dutch, dutch_kp` +- English: `english` (default), `light_english`, `lovins`, `minimal_english`, `porter2`, `possessive_english` +- Estonian: `estonian` +- Finnish: `finnish`, `light_finnish` +- French: `light_french`, `french`, `minimal_french` +- Galician: `galician`, `minimal_galician` (plural step only) +- German: `light_german`, `german`, `german2`, `minimal_german` +- Greek: `greek` +- Hindi: `hindi` +- Hungarian: `hungarian, light_hungarian` +- Indonesian: `indonesian` +- Irish: `irish` +- Italian: `light_italian, italian` +- Kurdish (Sorani): `sorani` +- Latvian: `latvian` +- Lithuanian: `lithuanian` +- Norwegian (Bokmål): `norwegian`, `light_norwegian`, `minimal_norwegian` +- Norwegian (Nynorsk): `light_nynorsk`, `minimal_nynorsk` +- Portuguese: `light_portuguese`, `minimal_portuguese`, `portuguese`, `portuguese_rslp` +- Romanian: `romanian` +- Russian: `russian`, `light_russian` +- Spanish: `light_spanish`, `spanish` +- Swedish: `swedish`, `light_swedish` +- Turkish: `turkish` + +You can also use the `name` parameter as an alias for the `language` parameter. If both are set, the `name` parameter is ignored. +{: .note} + +## Example + +The following example request creates a new index named `my-stemmer-index` and configures an analyzer with a `stemmer` filter: + +```json +PUT /my-stemmer-index +{ + "settings": { + "analysis": { + "filter": { + "my_english_stemmer": { + "type": "stemmer", + "language": "english" + } + }, + "analyzer": { + "my_stemmer_analyzer": { + "type": "custom", + "tokenizer": "standard", + "filter": [ + "lowercase", + "my_english_stemmer" + ] + } + } + } + } +} +``` +{% include copy-curl.html %} + +## Generated tokens + +Use the following request to examine the tokens generated using the analyzer: + +```json +GET /my-stemmer-index/_analyze +{ + "analyzer": "my_stemmer_analyzer", + "text": "running runs" +} +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + { + "token": "run", + "start_offset": 0, + "end_offset": 7, + "type": "", + "position": 0 + }, + { + "token": "run", + "start_offset": 8, + "end_offset": 12, + "type": "", + "position": 1 + } + ] +} +``` \ No newline at end of file From 8246556c2fb2856e7f159f6742365113f62ca873 Mon Sep 17 00:00:00 2001 From: AntonEliatra Date: Mon, 2 Dec 2024 17:30:39 +0000 Subject: [PATCH 12/69] Adding stemmer override token filter docs (#8455) * add stemmer_override token filter docs #8445 Signed-off-by: Anton Rubin * fixing dead link Signed-off-by: Anton Rubin * updating parameter table Signed-off-by: Anton Rubin * Doc review Signed-off-by: Fanit Kolchina * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --------- Signed-off-by: Anton Rubin Signed-off-by: Fanit Kolchina Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Fanit Kolchina Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Nathan Bower --- _analyzers/token-filters/index.md | 2 +- _analyzers/token-filters/stemmer-override.md | 139 +++++++++++++++++++ 2 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 _analyzers/token-filters/stemmer-override.md diff --git a/_analyzers/token-filters/index.md b/_analyzers/token-filters/index.md index f61c7db6e3..23c9a0232c 100644 --- a/_analyzers/token-filters/index.md +++ b/_analyzers/token-filters/index.md @@ -55,7 +55,7 @@ Token filter | Underlying Lucene token filter| Description [`shingle`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/shingle/) | [ShingleFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/shingle/ShingleFilter.html) | Generates shingles of lengths between `min_shingle_size` and `max_shingle_size` for tokens in the token stream. Shingles are similar to n-grams but are generated using words instead of letters. For example, two-word shingles added to the list of unigrams [`contribute`, `to`, `opensearch`] are [`contribute to`, `to opensearch`]. [`snowball`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/snowball/) | N/A | Stems words using a [Snowball-generated stemmer](https://snowballstem.org/). The `snowball` token filter supports using the following languages in the `language` field: `Arabic`, `Armenian`, `Basque`, `Catalan`, `Danish`, `Dutch`, `English`, `Estonian`, `Finnish`, `French`, `German`, `German2`, `Hungarian`, `Irish`, `Italian`, `Kp`, `Lithuanian`, `Lovins`, `Norwegian`, `Porter`, `Portuguese`, `Romanian`, `Russian`, `Spanish`, `Swedish`, `Turkish`. [`stemmer`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/stemmer/) | N/A | Provides algorithmic stemming for the following languages used in the `language` field: `arabic`, `armenian`, `basque`, `bengali`, `brazilian`, `bulgarian`, `catalan`, `czech`, `danish`, `dutch`, `dutch_kp`, `english`, `light_english`, `lovins`, `minimal_english`, `porter2`, `possessive_english`, `estonian`, `finnish`, `light_finnish`, `french`, `light_french`, `minimal_french`, `galician`, `minimal_galician`, `german`, `german2`, `light_german`, `minimal_german`, `greek`, `hindi`, `hungarian`, `light_hungarian`, `indonesian`, `irish`, `italian`, `light_italian`, `latvian`, `Lithuanian`, `norwegian`, `light_norwegian`, `minimal_norwegian`, `light_nynorsk`, `minimal_nynorsk`, `portuguese`, `light_portuguese`, `minimal_portuguese`, `portuguese_rslp`, `romanian`, `russian`, `light_russian`, `sorani`, `spanish`, `light_spanish`, `swedish`, `light_swedish`, `turkish`. -`stemmer_override` | N/A | Overrides stemming algorithms by applying a custom mapping so that the provided terms are not stemmed. +[`stemmer_override`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/stemmer-override/) | N/A | Overrides stemming algorithms by applying a custom mapping so that the provided terms are not stemmed. `stop` | [StopFilter](https://lucene.apache.org/core/8_7_0/core/org/apache/lucene/analysis/StopFilter.html) | Removes stop words from a token stream. [`synonym`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/synonym/) | N/A | Supplies a synonym list for the analysis process. The synonym list is provided using a configuration file. [`synonym_graph`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/synonym-graph/) | N/A | Supplies a synonym list, including multiword synonyms, for the analysis process. diff --git a/_analyzers/token-filters/stemmer-override.md b/_analyzers/token-filters/stemmer-override.md new file mode 100644 index 0000000000..c06f673714 --- /dev/null +++ b/_analyzers/token-filters/stemmer-override.md @@ -0,0 +1,139 @@ +--- +layout: default +title: Stemmer override +parent: Token filters +nav_order: 400 +--- + +# Stemmer override token filter + +The `stemmer_override` token filter allows you to define custom stemming rules that override the behavior of default stemmers like Porter or Snowball. This can be useful when you want to apply specific stemming behavior to certain words that might not be modified correctly by the standard stemming algorithms. + +## Parameters + +The `stemmer_override` token filter must be configured with exactly one of the following parameters. + +Parameter | Data type | Description +:--- | :--- | :--- +`rules` | String | Defines the override rules directly in the settings. +`rules_path` | String | Specifies the path to the file containing custom rules (mappings). The path can be either an absolute path or a path relative to the config directory. + +## Example + +The following example request creates a new index named `my-index` and configures an analyzer with a `stemmer_override` filter: + +```json +PUT /my-index +{ + "settings": { + "analysis": { + "filter": { + "my_stemmer_override_filter": { + "type": "stemmer_override", + "rules": [ + "running, runner => run", + "bought => buy", + "best => good" + ] + } + }, + "analyzer": { + "my_custom_analyzer": { + "type": "custom", + "tokenizer": "standard", + "filter": [ + "lowercase", + "my_stemmer_override_filter" + ] + } + } + } + } +} +``` +{% include copy-curl.html %} + +## Generated tokens + +Use the following request to examine the tokens generated using the analyzer: + +```json +GET /my-index/_analyze +{ + "analyzer": "my_custom_analyzer", + "text": "I am a runner and bought the best shoes" +} +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + { + "token": "i", + "start_offset": 0, + "end_offset": 1, + "type": "", + "position": 0 + }, + { + "token": "am", + "start_offset": 2, + "end_offset": 4, + "type": "", + "position": 1 + }, + { + "token": "a", + "start_offset": 5, + "end_offset": 6, + "type": "", + "position": 2 + }, + { + "token": "run", + "start_offset": 7, + "end_offset": 13, + "type": "", + "position": 3 + }, + { + "token": "and", + "start_offset": 14, + "end_offset": 17, + "type": "", + "position": 4 + }, + { + "token": "buy", + "start_offset": 18, + "end_offset": 24, + "type": "", + "position": 5 + }, + { + "token": "the", + "start_offset": 25, + "end_offset": 28, + "type": "", + "position": 6 + }, + { + "token": "good", + "start_offset": 29, + "end_offset": 33, + "type": "", + "position": 7 + }, + { + "token": "shoes", + "start_offset": 34, + "end_offset": 39, + "type": "", + "position": 8 + } + ] +} +``` \ No newline at end of file From 25195568ba0a8ab48901538938cd54be3a1d6cab Mon Sep 17 00:00:00 2001 From: AntonEliatra Date: Mon, 2 Dec 2024 18:02:48 +0000 Subject: [PATCH 13/69] add stop token filter docs #8446 (#8456) * add stop token filter docs #8446 Signed-off-by: Anton Rubin * updating parameter table Signed-off-by: Anton Rubin * Doc review Signed-off-by: Fanit Kolchina * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --------- Signed-off-by: Anton Rubin Signed-off-by: Fanit Kolchina Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Fanit Kolchina Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Nathan Bower --- _analyzers/token-filters/index.md | 2 +- _analyzers/token-filters/stop.md | 111 ++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 _analyzers/token-filters/stop.md diff --git a/_analyzers/token-filters/index.md b/_analyzers/token-filters/index.md index 23c9a0232c..5fe883828a 100644 --- a/_analyzers/token-filters/index.md +++ b/_analyzers/token-filters/index.md @@ -56,7 +56,7 @@ Token filter | Underlying Lucene token filter| Description [`snowball`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/snowball/) | N/A | Stems words using a [Snowball-generated stemmer](https://snowballstem.org/). The `snowball` token filter supports using the following languages in the `language` field: `Arabic`, `Armenian`, `Basque`, `Catalan`, `Danish`, `Dutch`, `English`, `Estonian`, `Finnish`, `French`, `German`, `German2`, `Hungarian`, `Irish`, `Italian`, `Kp`, `Lithuanian`, `Lovins`, `Norwegian`, `Porter`, `Portuguese`, `Romanian`, `Russian`, `Spanish`, `Swedish`, `Turkish`. [`stemmer`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/stemmer/) | N/A | Provides algorithmic stemming for the following languages used in the `language` field: `arabic`, `armenian`, `basque`, `bengali`, `brazilian`, `bulgarian`, `catalan`, `czech`, `danish`, `dutch`, `dutch_kp`, `english`, `light_english`, `lovins`, `minimal_english`, `porter2`, `possessive_english`, `estonian`, `finnish`, `light_finnish`, `french`, `light_french`, `minimal_french`, `galician`, `minimal_galician`, `german`, `german2`, `light_german`, `minimal_german`, `greek`, `hindi`, `hungarian`, `light_hungarian`, `indonesian`, `irish`, `italian`, `light_italian`, `latvian`, `Lithuanian`, `norwegian`, `light_norwegian`, `minimal_norwegian`, `light_nynorsk`, `minimal_nynorsk`, `portuguese`, `light_portuguese`, `minimal_portuguese`, `portuguese_rslp`, `romanian`, `russian`, `light_russian`, `sorani`, `spanish`, `light_spanish`, `swedish`, `light_swedish`, `turkish`. [`stemmer_override`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/stemmer-override/) | N/A | Overrides stemming algorithms by applying a custom mapping so that the provided terms are not stemmed. -`stop` | [StopFilter](https://lucene.apache.org/core/8_7_0/core/org/apache/lucene/analysis/StopFilter.html) | Removes stop words from a token stream. +[`stop`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/stop/) | [StopFilter](https://lucene.apache.org/core/8_7_0/core/org/apache/lucene/analysis/StopFilter.html) | Removes stop words from a token stream. [`synonym`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/synonym/) | N/A | Supplies a synonym list for the analysis process. The synonym list is provided using a configuration file. [`synonym_graph`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/synonym-graph/) | N/A | Supplies a synonym list, including multiword synonyms, for the analysis process. `trim` | [TrimFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/TrimFilter.html) | Trims leading and trailing white space from each token in a stream. diff --git a/_analyzers/token-filters/stop.md b/_analyzers/token-filters/stop.md new file mode 100644 index 0000000000..8f3e01b72d --- /dev/null +++ b/_analyzers/token-filters/stop.md @@ -0,0 +1,111 @@ +--- +layout: default +title: Stop +parent: Token filters +nav_order: 410 +--- + +# Stop token filter + +The `stop` token filter is used to remove common words (also known as _stopwords_) from a token stream during analysis. Stopwords are typically articles and prepositions, such as `a` or `for`. These words are not significantly meaningful in search queries and are often excluded to improve search efficiency and relevance. + +The default list of English stopwords includes the following words: `a`, `an`, `and`, `are`, `as`, `at`, `be`, `but`, `by`, `for`, `if`, `in`, `into`, `is`, `it`, `no`, `not`, `of`, `on`, `or`, `such`, `that`, `the`, `their`, `then`, `there`, `these`, `they`, `this`, `to`, `was`, `will`, and `with`. + +## Parameters + +The `stop` token filter can be configured with the following parameters. + +Parameter | Required/Optional | Data type | Description +:--- | :--- | :--- | :--- +`stopwords` | Optional | String | Specifies either a custom array of stopwords or a language for which to fetch the predefined Lucene stopword list:

- [`_arabic_`](https://github.com/apache/lucene/blob/main/lucene/analysis/common/src/resources/org/apache/lucene/analysis/ar/stopwords.txt)
- [`_armenian_`](https://github.com/apache/lucene/blob/main/lucene/analysis/common/src/resources/org/apache/lucene/analysis/hy/stopwords.txt)
- [`_basque_`](https://github.com/apache/lucene/blob/main/lucene/analysis/common/src/resources/org/apache/lucene/analysis/eu/stopwords.txt)
- [`_bengali_`](https://github.com/apache/lucene/blob/main/lucene/analysis/common/src/resources/org/apache/lucene/analysis/bn/stopwords.txt)
- [`_brazilian_` (Brazilian Portuguese)](https://github.com/apache/lucene/blob/main/lucene/analysis/common/src/resources/org/apache/lucene/analysis/br/stopwords.txt)
- [`_bulgarian_`](https://github.com/apache/lucene/blob/main/lucene/analysis/common/src/resources/org/apache/lucene/analysis/bg/stopwords.txt)
- [`_catalan_`](https://github.com/apache/lucene/blob/main/lucene/analysis/common/src/resources/org/apache/lucene/analysis/ca/stopwords.txt)
- [`_cjk_` (Chinese, Japanese, and Korean)](https://github.com/apache/lucene/blob/main/lucene/analysis/common/src/resources/org/apache/lucene/analysis/cjk/stopwords.txt)
- [`_czech_`](https://github.com/apache/lucene/blob/main/lucene/analysis/common/src/resources/org/apache/lucene/analysis/cz/stopwords.txt)
- [`_danish_`](https://github.com/apache/lucene/blob/main/lucene/analysis/common/src/resources/org/apache/lucene/analysis/snowball/danish_stop.txt)
- [`_dutch_`](https://github.com/apache/lucene/blob/main/lucene/analysis/common/src/resources/org/apache/lucene/analysis/snowball/dutch_stop.txt)
- [`_english_`](https://github.com/apache/lucene/blob/main/lucene/analysis/common/src/java/org/apache/lucene/analysis/en/EnglishAnalyzer.java#L48) (Default)
- [`_estonian_`](https://github.com/apache/lucene/blob/main/lucene/analysis/common/src/resources/org/apache/lucene/analysis/et/stopwords.txt)
- [`_finnish_`](https://github.com/apache/lucene/blob/main/lucene/analysis/common/src/resources/org/apache/lucene/analysis/snowball/finnish_stop.txt)
- [`_french_`](https://github.com/apache/lucene/blob/main/lucene/analysis/common/src/resources/org/apache/lucene/analysis/snowball/french_stop.txt)
- [`_galician_`](https://github.com/apache/lucene/blob/main/lucene/analysis/common/src/resources/org/apache/lucene/analysis/gl/stopwords.txt)
- [`_german_`](https://github.com/apache/lucene/blob/main/lucene/analysis/common/src/resources/org/apache/lucene/analysis/snowball/german_stop.txt)
- [`_greek_`](https://github.com/apache/lucene/blob/main/lucene/analysis/common/src/resources/org/apache/lucene/analysis/el/stopwords.txt)
- [`_hindi_`](https://github.com/apache/lucene/blob/main/lucene/analysis/common/src/resources/org/apache/lucene/analysis/hi/stopwords.txt)
- [`_hungarian_`](https://github.com/apache/lucene/blob/main/lucene/analysis/common/src/resources/org/apache/lucene/analysis/snowball/hungarian_stop.txt)
- [`_indonesian_`](https://github.com/apache/lucene/blob/main/lucene/analysis/common/src/resources/org/apache/lucene/analysis/id/stopwords.txt)
- [`_irish_`](https://github.com/apache/lucene/blob/main/lucene/analysis/common/src/resources/org/apache/lucene/analysis/ga/stopwords.txt)
- [`_italian_`](https://github.com/apache/lucene/blob/main/lucene/analysis/common/src/resources/org/apache/lucene/analysis/snowball/italian_stop.txt)
- [`_latvian_`](https://github.com/apache/lucene/blob/main/lucene/analysis/common/src/resources/org/apache/lucene/analysis/lv/stopwords.txt)
- [`_lithuanian_`](https://github.com/apache/lucene/blob/main/lucene/analysis/common/src/resources/org/apache/lucene/analysis/lt/stopwords.txt)
- [`_norwegian_`](https://github.com/apache/lucene/blob/main/lucene/analysis/common/src/resources/org/apache/lucene/analysis/snowball/norwegian_stop.txt)
- [`_persian_`](https://github.com/apache/lucene/blob/main/lucene/analysis/common/src/resources/org/apache/lucene/analysis/fa/stopwords.txt)
- [`_portuguese_`](https://github.com/apache/lucene/blob/main/lucene/analysis/common/src/resources/org/apache/lucene/analysis/snowball/portuguese_stop.txt)
- [`_romanian_`](https://github.com/apache/lucene/blob/main/lucene/analysis/common/src/resources/org/apache/lucene/analysis/ro/stopwords.txt)
- [`_russian_`](https://github.com/apache/lucene/blob/main/lucene/analysis/common/src/resources/org/apache/lucene/analysis/snowball/russian_stop.txt)
- [`_sorani_`](https://github.com/apache/lucene/blob/main/lucene/analysis/common/src/resources/org/apache/lucene/analysis/sr/stopwords.txt)
- [`_spanish_`](https://github.com/apache/lucene/blob/main/lucene/analysis/common/src/resources/org/apache/lucene/analysis/ckb/stopwords.txt)
- [`_swedish_`](https://github.com/apache/lucene/blob/main/lucene/analysis/common/src/resources/org/apache/lucene/analysis/snowball/swedish_stop.txt)
- [`_thai_`](https://github.com/apache/lucene/blob/main/lucene/analysis/common/src/resources/org/apache/lucene/analysis/th/stopwords.txt)
- [`_turkish_`](https://github.com/apache/lucene/blob/main/lucene/analysis/common/src/resources/org/apache/lucene/analysis/tr/stopwords.txt) +`stopwords_path` | Optional | String | Specifies the file path (absolute or relative to the config directory) of the file containing custom stopwords. +`ignore_case` | Optional | Boolean | If `true`, stopwords will be matched regardless of their case. Default is `false`. +`remove_trailing` | Optional | Boolean | If `true`, trailing stopwords will be removed during analysis. Default is `true`. + +## Example + +The following example request creates a new index named `my-stopword-index` and configures an analyzer with a `stop` filter that uses the predefined stopword list for the English language: + +```json +PUT /my-stopword-index +{ + "settings": { + "analysis": { + "filter": { + "my_stop_filter": { + "type": "stop", + "stopwords": "_english_" + } + }, + "analyzer": { + "my_stop_analyzer": { + "type": "custom", + "tokenizer": "standard", + "filter": [ + "lowercase", + "my_stop_filter" + ] + } + } + } + } +} +``` +{% include copy-curl.html %} + +## Generated tokens + +Use the following request to examine the tokens generated using the analyzer: + +```json +GET /my-stopword-index/_analyze +{ + "analyzer": "my_stop_analyzer", + "text": "A quick dog jumps over the turtle" +} +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + { + "token": "quick", + "start_offset": 2, + "end_offset": 7, + "type": "", + "position": 1 + }, + { + "token": "dog", + "start_offset": 8, + "end_offset": 11, + "type": "", + "position": 2 + }, + { + "token": "jumps", + "start_offset": 12, + "end_offset": 17, + "type": "", + "position": 3 + }, + { + "token": "over", + "start_offset": 18, + "end_offset": 22, + "type": "", + "position": 4 + }, + { + "token": "turtle", + "start_offset": 27, + "end_offset": 33, + "type": "", + "position": 6 + } + ] +} +``` \ No newline at end of file From deac8028c12a8d6a6ec398be355c1e640ccde2b9 Mon Sep 17 00:00:00 2001 From: AntonEliatra Date: Mon, 2 Dec 2024 20:16:47 +0000 Subject: [PATCH 14/69] add trim token filter docs #8449 (#8461) * add trim token filter docs #8449 Signed-off-by: Anton Rubin * updating the nav_order Signed-off-by: Anton Rubin * Doc review Signed-off-by: Fanit Kolchina * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --------- Signed-off-by: Anton Rubin Signed-off-by: Fanit Kolchina Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Fanit Kolchina Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Nathan Bower --- _analyzers/token-filters/index.md | 2 +- _analyzers/token-filters/trim.md | 93 +++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 _analyzers/token-filters/trim.md diff --git a/_analyzers/token-filters/index.md b/_analyzers/token-filters/index.md index 5fe883828a..9e6d6ccd00 100644 --- a/_analyzers/token-filters/index.md +++ b/_analyzers/token-filters/index.md @@ -59,7 +59,7 @@ Token filter | Underlying Lucene token filter| Description [`stop`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/stop/) | [StopFilter](https://lucene.apache.org/core/8_7_0/core/org/apache/lucene/analysis/StopFilter.html) | Removes stop words from a token stream. [`synonym`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/synonym/) | N/A | Supplies a synonym list for the analysis process. The synonym list is provided using a configuration file. [`synonym_graph`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/synonym-graph/) | N/A | Supplies a synonym list, including multiword synonyms, for the analysis process. -`trim` | [TrimFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/TrimFilter.html) | Trims leading and trailing white space from each token in a stream. +[`trim`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/trim/) | [TrimFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/TrimFilter.html) | Trims leading and trailing white space characters from each token in a stream. `truncate` | [TruncateTokenFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/TruncateTokenFilter.html) | Truncates tokens whose length exceeds the specified character limit. `unique` | N/A | Ensures each token is unique by removing duplicate tokens from a stream. `uppercase` | [UpperCaseFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/core/LowerCaseFilter.html) | Converts tokens to uppercase. diff --git a/_analyzers/token-filters/trim.md b/_analyzers/token-filters/trim.md new file mode 100644 index 0000000000..cdfebed52f --- /dev/null +++ b/_analyzers/token-filters/trim.md @@ -0,0 +1,93 @@ +--- +layout: default +title: Trim +parent: Token filters +nav_order: 430 +--- + +# Trim token filter + +The `trim` token filter removes leading and trailing white space characters from tokens. + +Many popular tokenizers, such as `standard`, `keyword`, and `whitespace` tokenizers, automatically strip leading and trailing white space characters during tokenization. When using these tokenizers, there is no need to configure an additional `trim` token filter. +{: .note} + + +## Example + +The following example request creates a new index named `my_pattern_trim_index` and configures an analyzer with a `trim` filter and a `pattern` tokenizer, which does not remove leading and trailing white space characters: + +```json +PUT /my_pattern_trim_index +{ + "settings": { + "analysis": { + "filter": { + "my_trim_filter": { + "type": "trim" + } + }, + "tokenizer": { + "my_pattern_tokenizer": { + "type": "pattern", + "pattern": "," + } + }, + "analyzer": { + "my_pattern_trim_analyzer": { + "type": "custom", + "tokenizer": "my_pattern_tokenizer", + "filter": [ + "lowercase", + "my_trim_filter" + ] + } + } + } + } +} +``` +{% include copy-curl.html %} + +## Generated tokens + +Use the following request to examine the tokens generated using the analyzer: + +```json +GET /my_pattern_trim_index/_analyze +{ + "analyzer": "my_pattern_trim_analyzer", + "text": " OpenSearch , is , powerful " +} +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + { + "token": "opensearch", + "start_offset": 0, + "end_offset": 12, + "type": "word", + "position": 0 + }, + { + "token": "is", + "start_offset": 13, + "end_offset": 18, + "type": "word", + "position": 1 + }, + { + "token": "powerful", + "start_offset": 19, + "end_offset": 32, + "type": "word", + "position": 2 + } + ] +} +``` From d0e457330acd278cb2887451775d50b14c8ce399 Mon Sep 17 00:00:00 2001 From: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Date: Mon, 2 Dec 2024 17:01:02 -0500 Subject: [PATCH 15/69] Update painless-functions.md (#8852) Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --- _search-plugins/knn/painless-functions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_search-plugins/knn/painless-functions.md b/_search-plugins/knn/painless-functions.md index 7a8d9fec7b..4b2311ad65 100644 --- a/_search-plugins/knn/painless-functions.md +++ b/_search-plugins/knn/painless-functions.md @@ -51,7 +51,7 @@ The following table describes the available painless functions the k-NN plugin p Function name | Function signature | Description :--- | :--- l2Squared | `float l2Squared (float[] queryVector, doc['vector field'])` | This function calculates the square of the L2 distance (Euclidean distance) between a given query vector and document vectors. The shorter the distance, the more relevant the document is, so this example inverts the return value of the l2Squared function. If the document vector matches the query vector, the result is 0, so this example also adds 1 to the distance to avoid divide by zero errors. -l1Norm | `float l1Norm (float[] queryVector, doc['vector field'])` | This function calculates the square of the L2 distance (Euclidean distance) between a given query vector and document vectors. The shorter the distance, the more relevant the document is, so this example inverts the return value of the l2Squared function. If the document vector matches the query vector, the result is 0, so this example also adds 1 to the distance to avoid divide by zero errors. +l1Norm | `float l1Norm (float[] queryVector, doc['vector field'])` | This function calculates the L1 Norm distance (Manhattan distance) between a given query vector and document vectors. cosineSimilarity | `float cosineSimilarity (float[] queryVector, doc['vector field'])` | Cosine similarity is an inner product of the query vector and document vector normalized to both have a length of 1. If the magnitude of the query vector doesn't change throughout the query, you can pass the magnitude of the query vector to improve performance, instead of calculating the magnitude every time for every filtered document:
`float cosineSimilarity (float[] queryVector, doc['vector field'], float normQueryVector)`
In general, the range of cosine similarity is [-1, 1]. However, in the case of information retrieval, the cosine similarity of two documents ranges from 0 to 1 because the tf-idf statistic can't be negative. Therefore, the k-NN plugin adds 1.0 in order to always yield a positive cosine similarity score. hamming | `float hamming (float[] queryVector, doc['vector field'])` | This function calculates the Hamming distance between a given query vector and document vectors. The Hamming distance is the number of positions at which the corresponding elements are different. The shorter the distance, the more relevant the document is, so this example inverts the return value of the Hamming distance. @@ -73,4 +73,4 @@ The `hamming` space type is supported for binary vectors in OpenSearch version 2 Because scores can only be positive, this script ranks documents with vector fields higher than those without. With cosine similarity, it is not valid to pass a zero vector (`[0, 0, ...]`) as input. This is because the magnitude of such a vector is 0, which raises a `divide by 0` exception in the corresponding formula. Requests containing the zero vector will be rejected, and a corresponding exception will be thrown. -{: .note } \ No newline at end of file +{: .note } From 22563d979151b883dff770de66518680cc88982a Mon Sep 17 00:00:00 2001 From: AntonEliatra Date: Tue, 3 Dec 2024 12:53:43 +0000 Subject: [PATCH 16/69] add uppercase token filter docs #8452 (#8464) * add uppercase token filter docs #8452 Signed-off-by: Anton Rubin * Doc review Signed-off-by: Fanit Kolchina --------- Signed-off-by: Anton Rubin Signed-off-by: Fanit Kolchina Co-authored-by: Fanit Kolchina --- _analyzers/token-filters/index.md | 2 +- _analyzers/token-filters/uppercase.md | 83 +++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 _analyzers/token-filters/uppercase.md diff --git a/_analyzers/token-filters/index.md b/_analyzers/token-filters/index.md index 9e6d6ccd00..1203771639 100644 --- a/_analyzers/token-filters/index.md +++ b/_analyzers/token-filters/index.md @@ -62,6 +62,6 @@ Token filter | Underlying Lucene token filter| Description [`trim`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/trim/) | [TrimFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/TrimFilter.html) | Trims leading and trailing white space characters from each token in a stream. `truncate` | [TruncateTokenFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/TruncateTokenFilter.html) | Truncates tokens whose length exceeds the specified character limit. `unique` | N/A | Ensures each token is unique by removing duplicate tokens from a stream. -`uppercase` | [UpperCaseFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/core/LowerCaseFilter.html) | Converts tokens to uppercase. +[`uppercase`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/uppercase/) | [UpperCaseFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/core/LowerCaseFilter.html) | Converts tokens to uppercase. `word_delimiter` | [WordDelimiterFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/WordDelimiterFilter.html) | Splits tokens at non-alphanumeric characters and performs normalization based on the specified rules. `word_delimiter_graph` | [WordDelimiterGraphFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/WordDelimiterGraphFilter.html) | Splits tokens at non-alphanumeric characters and performs normalization based on the specified rules. Assigns multi-position tokens a `positionLength` attribute. diff --git a/_analyzers/token-filters/uppercase.md b/_analyzers/token-filters/uppercase.md new file mode 100644 index 0000000000..5026892400 --- /dev/null +++ b/_analyzers/token-filters/uppercase.md @@ -0,0 +1,83 @@ +--- +layout: default +title: Uppercase +parent: Token filters +nav_order: 460 +--- + +# Uppercase token filter + +The `uppercase` token filter is used to convert all tokens (words) to uppercase during analysis. + +## Example + +The following example request creates a new index named `uppercase_example` and configures an analyzer with an `uppercase` filter: + +```json +PUT /uppercase_example +{ + "settings": { + "analysis": { + "filter": { + "uppercase_filter": { + "type": "uppercase" + } + }, + "analyzer": { + "uppercase_analyzer": { + "type": "custom", + "tokenizer": "standard", + "filter": [ + "lowercase", + "uppercase_filter" + ] + } + } + } + } +} +``` +{% include copy-curl.html %} + +## Generated tokens + +Use the following request to examine the tokens generated using the analyzer: + +```json +GET /uppercase_example/_analyze +{ + "analyzer": "uppercase_analyzer", + "text": "OpenSearch is powerful" +} +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + { + "token": "OPENSEARCH", + "start_offset": 0, + "end_offset": 10, + "type": "", + "position": 0 + }, + { + "token": "IS", + "start_offset": 11, + "end_offset": 13, + "type": "", + "position": 1 + }, + { + "token": "POWERFUL", + "start_offset": 14, + "end_offset": 22, + "type": "", + "position": 2 + } + ] +} +``` From 0ab0751ce6cd151f787864f1c4e93711c1538ff4 Mon Sep 17 00:00:00 2001 From: AntonEliatra Date: Tue, 3 Dec 2024 12:57:38 +0000 Subject: [PATCH 17/69] add truncate token filter docs #8450 (#8462) * add truncate token filter docs #8450 Signed-off-by: Anton Rubin * updating parameter table Signed-off-by: Anton Rubin * Doc review Signed-off-by: Fanit Kolchina * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --------- Signed-off-by: Anton Rubin Signed-off-by: Fanit Kolchina Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Fanit Kolchina Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Nathan Bower --- _analyzers/token-filters/index.md | 2 +- _analyzers/token-filters/truncate.md | 107 +++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 _analyzers/token-filters/truncate.md diff --git a/_analyzers/token-filters/index.md b/_analyzers/token-filters/index.md index 1203771639..e2d3ae1df3 100644 --- a/_analyzers/token-filters/index.md +++ b/_analyzers/token-filters/index.md @@ -60,7 +60,7 @@ Token filter | Underlying Lucene token filter| Description [`synonym`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/synonym/) | N/A | Supplies a synonym list for the analysis process. The synonym list is provided using a configuration file. [`synonym_graph`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/synonym-graph/) | N/A | Supplies a synonym list, including multiword synonyms, for the analysis process. [`trim`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/trim/) | [TrimFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/TrimFilter.html) | Trims leading and trailing white space characters from each token in a stream. -`truncate` | [TruncateTokenFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/TruncateTokenFilter.html) | Truncates tokens whose length exceeds the specified character limit. +[`truncate`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/truncate/) | [TruncateTokenFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/TruncateTokenFilter.html) | Truncates tokens with lengths exceeding the specified character limit. `unique` | N/A | Ensures each token is unique by removing duplicate tokens from a stream. [`uppercase`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/uppercase/) | [UpperCaseFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/core/LowerCaseFilter.html) | Converts tokens to uppercase. `word_delimiter` | [WordDelimiterFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/WordDelimiterFilter.html) | Splits tokens at non-alphanumeric characters and performs normalization based on the specified rules. diff --git a/_analyzers/token-filters/truncate.md b/_analyzers/token-filters/truncate.md new file mode 100644 index 0000000000..16d1452901 --- /dev/null +++ b/_analyzers/token-filters/truncate.md @@ -0,0 +1,107 @@ +--- +layout: default +title: Truncate +parent: Token filters +nav_order: 440 +--- + +# Truncate token filter + +The `truncate` token filter is used to shorten tokens exceeding a specified length. It trims tokens to a maximum number of characters, ensuring that tokens exceeding this limit are truncated. + +## Parameters + +The `truncate` token filter can be configured with the following parameter. + +Parameter | Required/Optional | Data type | Description +:--- | :--- | :--- | :--- +`length` | Optional | Integer | Specifies the maximum length of the generated token. Default is `10`. + +## Example + +The following example request creates a new index named `truncate_example` and configures an analyzer with a `truncate` filter: + +```json +PUT /truncate_example +{ + "settings": { + "analysis": { + "filter": { + "truncate_filter": { + "type": "truncate", + "length": 5 + } + }, + "analyzer": { + "truncate_analyzer": { + "type": "custom", + "tokenizer": "standard", + "filter": [ + "lowercase", + "truncate_filter" + ] + } + } + } + } +} +``` +{% include copy-curl.html %} + +## Generated tokens + +Use the following request to examine the tokens generated using the analyzer: + +```json +GET /truncate_example/_analyze +{ + "analyzer": "truncate_analyzer", + "text": "OpenSearch is powerful and scalable" +} + +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + { + "token": "opens", + "start_offset": 0, + "end_offset": 10, + "type": "", + "position": 0 + }, + { + "token": "is", + "start_offset": 11, + "end_offset": 13, + "type": "", + "position": 1 + }, + { + "token": "power", + "start_offset": 14, + "end_offset": 22, + "type": "", + "position": 2 + }, + { + "token": "and", + "start_offset": 23, + "end_offset": 26, + "type": "", + "position": 3 + }, + { + "token": "scala", + "start_offset": 27, + "end_offset": 35, + "type": "", + "position": 4 + } + ] +} +``` From 3f756d2512a338f34854da4075e7840c99e31ca4 Mon Sep 17 00:00:00 2001 From: AntonEliatra Date: Tue, 3 Dec 2024 14:22:19 +0000 Subject: [PATCH 18/69] add word delimiter graph token filter docs #8454 (#8468) * add word delimiter graph token filter docs #8454 Signed-off-by: Anton Rubin * updating parameter table Signed-off-by: Anton Rubin * Doc review Signed-off-by: Fanit Kolchina * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> * Editorial comments Signed-off-by: Fanit Kolchina * More merge conflicts Signed-off-by: Fanit Kolchina * typo fix Signed-off-by: Fanit Kolchina --------- Signed-off-by: Anton Rubin Signed-off-by: Fanit Kolchina Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Fanit Kolchina Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Nathan Bower --- _analyzers/token-filters/index.md | 2 +- .../token-filters/word-delimiter-graph.md | 164 ++++++++++++++++++ 2 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 _analyzers/token-filters/word-delimiter-graph.md diff --git a/_analyzers/token-filters/index.md b/_analyzers/token-filters/index.md index e2d3ae1df3..21b4c3c7fa 100644 --- a/_analyzers/token-filters/index.md +++ b/_analyzers/token-filters/index.md @@ -64,4 +64,4 @@ Token filter | Underlying Lucene token filter| Description `unique` | N/A | Ensures each token is unique by removing duplicate tokens from a stream. [`uppercase`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/uppercase/) | [UpperCaseFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/core/LowerCaseFilter.html) | Converts tokens to uppercase. `word_delimiter` | [WordDelimiterFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/WordDelimiterFilter.html) | Splits tokens at non-alphanumeric characters and performs normalization based on the specified rules. -`word_delimiter_graph` | [WordDelimiterGraphFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/WordDelimiterGraphFilter.html) | Splits tokens at non-alphanumeric characters and performs normalization based on the specified rules. Assigns multi-position tokens a `positionLength` attribute. +[`word_delimiter_graph`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/word-delimiter-graph/) | [WordDelimiterGraphFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/WordDelimiterGraphFilter.html) | Splits tokens at non-alphanumeric characters and performs normalization based on the specified rules. Assigns a `positionLength` attribute to multi-position tokens. diff --git a/_analyzers/token-filters/word-delimiter-graph.md b/_analyzers/token-filters/word-delimiter-graph.md new file mode 100644 index 0000000000..ac734bebeb --- /dev/null +++ b/_analyzers/token-filters/word-delimiter-graph.md @@ -0,0 +1,164 @@ +--- +layout: default +title: Word delimiter graph +parent: Token filters +nav_order: 480 +--- + +# Word delimiter graph token filter + +The `word_delimiter_graph` token filter is used to split tokens at predefined characters and also offers optional token normalization based on customizable rules. + +The `word_delimiter_graph` filter is used to remove punctuation from complex identifiers like part numbers or product IDs. In such cases, it is best used with the `keyword` tokenizer. For hyphenated words, use the `synonym_graph` token filter instead of the `word_delimiter_graph` filter because users frequently search for these terms both with and without hyphens. +{: .note} + +By default, the filter applies the following rules. + +| Description | Input | Output | +|:---|:---|:---| +| Treats non-alphanumeric characters as delimiters. | `ultra-fast` | `ultra`, `fast` | +| Removes delimiters at the beginning or end of tokens. | `Z99++'Decoder'`| `Z99`, `Decoder` | +| Splits tokens when there is a transition between uppercase and lowercase letters. | `OpenSearch` | `Open`, `Search` | +| Splits tokens when there is a transition between letters and numbers. | `T1000` | `T`, `1000` | +| Removes the possessive ('s) from the end of tokens. | `John's` | `John` | + +It's important **not** to use tokenizers that strip punctuation, like the `standard` tokenizer, with this filter. Doing so may prevent proper token splitting and interfere with options like `catenate_all` or `preserve_original`. We recommend using this filter with a `keyword` or `whitespace` tokenizer. +{: .important} + +## Parameters + +You can configure the `word_delimiter_graph` token filter using the following parameters. + +Parameter | Required/Optional | Data type | Description +:--- | :--- | :--- | :--- +`adjust_offsets` | Optional | Boolean | Determines whether the token offsets should be recalculated for split or concatenated tokens. When `true`, the filter adjusts the token offsets to accurately represent the token's position within the token stream. This adjustment ensures that the token's location in the text aligns with its modified form after processing, which is particularly useful for applications like highlighting or phrase queries. When `false`, the offsets remain unchanged, which may result in misalignment when the processed tokens are mapped back to their positions in the original text. If your analyzer uses filters like `trim` that change the token lengths without changing their offsets, we recommend setting this parameter to `false`. Default is `true`. +`catenate_all` | Optional | Boolean | Produces concatenated tokens from a sequence of alphanumeric parts. For example, `"quick-fast-200"` becomes `[ quickfast200, quick, fast, 200 ]`. Default is `false`. +`catenate_numbers` | Optional | Boolean | Concatenates numerical sequences. For example, `"10-20-30"` becomes `[ 102030, 10, 20, 30 ]`. Default is `false`. +`catenate_words` | Optional | Boolean | Concatenates alphabetic words. For example, `"high-speed-level"` becomes `[ highspeedlevel, high, speed, level ]`. Default is `false`. +`generate_number_parts` | Optional | Boolean | If `true`, numeric tokens (tokens consisting of numbers only) are included in the output. Default is `true`. +`generate_word_parts` | Optional | Boolean | If `true`, alphabetical tokens (tokens consisting of alphabetic characters only) are included in the output. Default is `true`. +`ignore_keywords` | Optional | Boolean | Whether to process tokens marked as keywords. Default is `false`. +`preserve_original` | Optional | Boolean | Keeps the original token (which may include non-alphanumeric delimiters) alongside the generated tokens in the output. For example, `"auto-drive-300"` becomes `[ auto-drive-300, auto, drive, 300 ]`. If `true`, the filter generates multi-position tokens not supported by indexing, so do not use this filter in an index analyzer or use the `flatten_graph` filter after this filter. Default is `false`. +`protected_words` | Optional | Array of strings | Specifies tokens that should not be split. +`protected_words_path` | Optional | String | Specifies a path (absolute or relative to the config directory) to a file containing tokens that should not be separated by new lines. +`split_on_case_change` | Optional | Boolean | Splits tokens where consecutive letters have different cases (one is lowercase and the other is uppercase). For example, `"OpenSearch"` becomes `[ Open, Search ]`. Default is `true`. +`split_on_numerics` | Optional | Boolean | Splits tokens where there are consecutive letters and numbers. For example `"v8engine"` will become `[ v, 8, engine ]`. Default is `true`. +`stem_english_possessive` | Optional | Boolean | Removes English possessive endings, such as `'s`. Default is `true`. +`type_table` | Optional | Array of strings | A custom map that specifies how to treat characters and whether to treat them as delimiters, which avoids unwanted splitting. For example, to treat a hyphen (`-`) as an alphanumeric character, specify `["- => ALPHA"]` so that words are not split at hyphens. Valid types are:
- `ALPHA`: alphabetical
- `ALPHANUM`: alphanumeric
- `DIGIT`: numeric
- `LOWER`: lowercase alphabetical
- `SUBWORD_DELIM`: non-alphanumeric delimiter
- `UPPER`: uppercase alphabetical +`type_table_path` | Optional | String | Specifies a path (absolute or relative to the config directory) to a file containing a custom character map. The map specifies how to treat characters and whether to treat them as delimiters, which avoids unwanted splitting. For valid types, see `type_table`. + +## Example + +The following example request creates a new index named `my-custom-index` and configures an analyzer with a `word_delimiter_graph` filter: + +```json +PUT /my-custom-index +{ + "settings": { + "analysis": { + "analyzer": { + "custom_analyzer": { + "tokenizer": "keyword", + "filter": [ "custom_word_delimiter_filter" ] + } + }, + "filter": { + "custom_word_delimiter_filter": { + "type": "word_delimiter_graph", + "split_on_case_change": true, + "split_on_numerics": true, + "stem_english_possessive": true + } + } + } + } +} +``` +{% include copy-curl.html %} + +## Generated tokens + +Use the following request to examine the tokens generated using the analyzer: + +```json +GET /my-custom-index/_analyze +{ + "analyzer": "custom_analyzer", + "text": "FastCar's Model2023" +} +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + { + "token": "Fast", + "start_offset": 0, + "end_offset": 4, + "type": "word", + "position": 0 + }, + { + "token": "Car", + "start_offset": 4, + "end_offset": 7, + "type": "word", + "position": 1 + }, + { + "token": "Model", + "start_offset": 10, + "end_offset": 15, + "type": "word", + "position": 2 + }, + { + "token": "2023", + "start_offset": 15, + "end_offset": 19, + "type": "word", + "position": 3 + } + ] +} +``` + + +## Differences between the word_delimiter_graph and word_delimiter filters + + +Both the `word_delimiter_graph` and `word_delimiter` token filters generate tokens spanning multiple positions when any of the following parameters are set to `true`: + +- `catenate_all` +- `catenate_numbers` +- `catenate_words` +- `preserve_original` + +To illustrate the differences between these filters, consider the input text `Pro-XT500`. + + +### word_delimiter_graph + + +The `word_delimiter_graph` filter assigns a `positionLength` attribute to multi-position tokens, indicating how many positions a token spans. This ensures that the filter always generates valid token graphs, making it suitable for use in advanced token graph scenarios. Although token graphs with multi-position tokens are not supported for indexing, they can still be useful in search scenarios. For example, queries like `match_phrase` can use these graphs to generate multiple subqueries from a single input string. For the example input text, the `word_delimiter_graph` filter generates the following tokens: + +- `Pro` (position 1) +- `XT500` (position 2) +- `ProXT500` (position 1, `positionLength`: 2) + +The `positionLength` attribute the production of a valid graph to be used in advanced queries. + + +### word_delimiter + + +In contrast, the `word_delimiter` filter does not assign a `positionLength` attribute to multi-position tokens, leading to invalid graphs when these tokens are present. For the example input text, the `word_delimiter` filter generates the following tokens: + +- `Pro` (position 1) +- `XT500` (position 2) +- `ProXT500` (position 1, no `positionLength`) + +The lack of a `positionLength` attribute results in a token graph that is invalid for token streams containing multi-position tokens. \ No newline at end of file From ac852d62c19406a76a63a901fb333ab5fbd2f6d2 Mon Sep 17 00:00:00 2001 From: AntonEliatra Date: Tue, 3 Dec 2024 14:26:47 +0000 Subject: [PATCH 19/69] add kstem token filter docs #8150 (#8473) * add kstem token filter docs #8150 Signed-off-by: Anton Rubin * Doc review Signed-off-by: Fanit Kolchina * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --------- Signed-off-by: Anton Rubin Signed-off-by: Fanit Kolchina Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Fanit Kolchina Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Nathan Bower --- _analyzers/token-filters/index.md | 2 +- _analyzers/token-filters/kstem.md | 92 +++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 _analyzers/token-filters/kstem.md diff --git a/_analyzers/token-filters/index.md b/_analyzers/token-filters/index.md index 21b4c3c7fa..a25acf6ec4 100644 --- a/_analyzers/token-filters/index.md +++ b/_analyzers/token-filters/index.md @@ -36,7 +36,7 @@ Token filter | Underlying Lucene token filter| Description [`keep_words`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/keep-words/) | [KeepWordFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/KeepWordFilter.html) | Checks the tokens against the specified word list and keeps only those that are in the list. [`keyword_marker`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/keyword-marker/) | [KeywordMarkerFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/KeywordMarkerFilter.html) | Marks specified tokens as keywords, preventing them from being stemmed. `keyword_repeat` | [KeywordRepeatFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/KeywordRepeatFilter.html) | Emits each incoming token twice: once as a keyword and once as a non-keyword. -`kstem` | [KStemFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/en/KStemFilter.html) | Provides kstem-based stemming for the English language. Combines algorithmic stemming with a built-in dictionary. +[`kstem`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/kstem/) | [KStemFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/en/KStemFilter.html) | Provides KStem-based stemming for the English language. Combines algorithmic stemming with a built-in dictionary. `kuromoji_completion` | [JapaneseCompletionFilter](https://lucene.apache.org/core/9_10_0/analysis/kuromoji/org/apache/lucene/analysis/ja/JapaneseCompletionFilter.html) | Adds Japanese romanized terms to the token stream (in addition to the original tokens). Usually used to support autocomplete on Japanese search terms. Note that the filter has a `mode` parameter, which should be set to `index` when used in an index analyzer and `query` when used in a search analyzer. Requires the `analysis-kuromoji` plugin. For information about installing the plugin, see [Additional plugins]({{site.url}}{{site.baseurl}}/install-and-configure/plugins/#additional-plugins). [`length`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/length/) | [LengthFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/LengthFilter.html) | Removes tokens that are shorter or longer than the length range specified by `min` and `max`. [`limit`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/limit/) | [LimitTokenCountFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/LimitTokenCountFilter.html) | Limits the number of output tokens. For example, document field value sizes can be limited based on the token count. diff --git a/_analyzers/token-filters/kstem.md b/_analyzers/token-filters/kstem.md new file mode 100644 index 0000000000..d13fd2c675 --- /dev/null +++ b/_analyzers/token-filters/kstem.md @@ -0,0 +1,92 @@ +--- +layout: default +title: KStem +parent: Token filters +nav_order: 220 +--- + +# KStem token filter + +The `kstem` token filter is a stemming filter used to reduce words to their root forms. The filter is a lightweight algorithmic stemmer designed for the English language that performs the following stemming operations: + +- Reduces plurals to their singular form. +- Converts different verb tenses to their base form. +- Removes common derivational endings, such as "-ing" or "-ed". + +The `kstem` token filter is equivalent to the a `stemmer` filter configured with a `light_english` language. It provides a more conservative stemming compared to other stemming filters like `porter_stem`. + +The `kstem` token filter is based on the Lucene KStemFilter. For more information, see the [Lucene documentation](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/en/KStemFilter.html). + +## Example + +The following example request creates a new index named `my_kstem_index` and configures an analyzer with a `kstem` filter: + +```json +PUT /my_kstem_index +{ + "settings": { + "analysis": { + "filter": { + "kstem_filter": { + "type": "kstem" + } + }, + "analyzer": { + "my_kstem_analyzer": { + "type": "custom", + "tokenizer": "standard", + "filter": [ + "lowercase", + "kstem_filter" + ] + } + } + } + }, + "mappings": { + "properties": { + "content": { + "type": "text", + "analyzer": "my_kstem_analyzer" + } + } + } +} +``` +{% include copy-curl.html %} + +## Generated tokens + +Use the following request to examine the tokens generated using the analyzer: + +```json +POST /my_kstem_index/_analyze +{ + "analyzer": "my_kstem_analyzer", + "text": "stops stopped" +} +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + { + "token": "stop", + "start_offset": 0, + "end_offset": 5, + "type": "", + "position": 0 + }, + { + "token": "stop", + "start_offset": 6, + "end_offset": 13, + "type": "", + "position": 1 + } + ] +} +``` \ No newline at end of file From fc8663f42f2412065be5bbaacdc9711ca745bd53 Mon Sep 17 00:00:00 2001 From: AntonEliatra Date: Tue, 3 Dec 2024 14:35:20 +0000 Subject: [PATCH 20/69] adding flatten-graph token filter docs #8060 (#8742) * adding flatten-graph token filter docs #8060 Signed-off-by: Anton Rubin * Doc review Signed-off-by: Fanit Kolchina * Add warning Signed-off-by: Fanit Kolchina * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --------- Signed-off-by: Anton Rubin Signed-off-by: Fanit Kolchina Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Fanit Kolchina Co-authored-by: Nathan Bower --- _analyzers/token-filters/flatten-graph.md | 109 ++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 _analyzers/token-filters/flatten-graph.md diff --git a/_analyzers/token-filters/flatten-graph.md b/_analyzers/token-filters/flatten-graph.md new file mode 100644 index 0000000000..8d51c57400 --- /dev/null +++ b/_analyzers/token-filters/flatten-graph.md @@ -0,0 +1,109 @@ +--- +layout: default +title: Flatten graph +parent: Token filters +nav_order: 150 +--- + +# Flatten graph token filter + +The `flatten_graph` token filter is used to handle complex token relationships that occur when multiple tokens are generated at the same position in a graph structure. Some token filters, like `synonym_graph` and `word_delimiter_graph`, generate multi-position tokens---tokens that overlap or span multiple positions. These token graphs are useful for search queries but are not directly supported during indexing. The `flatten_graph` token filter resolves multi-position tokens into a linear sequence of tokens. Flattening the graph ensures compatibility with the indexing process. + +Token graph flattening is a lossy process. Whenever possible, avoid using the `flatten_graph` filter. Instead, apply graph token filters exclusively in search analyzers, removing the need for the `flatten_graph` filter. +{: .important} + +## Example + +The following example request creates a new index named `test_index` and configures an analyzer with a `flatten_graph` filter: + +```json +PUT /test_index +{ + "settings": { + "analysis": { + "analyzer": { + "my_index_analyzer": { + "type": "custom", + "tokenizer": "standard", + "filter": [ + "my_custom_filter", + "flatten_graph" + ] + } + }, + "filter": { + "my_custom_filter": { + "type": "word_delimiter_graph", + "catenate_all": true + } + } + } + } +} +``` +{% include copy-curl.html %} + +## Generated tokens + +Use the following request to examine the tokens generated using the analyzer: + +```json +POST /test_index/_analyze +{ + "analyzer": "my_index_analyzer", + "text": "OpenSearch helped many employers" +} +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + { + "token": "OpenSearch", + "start_offset": 0, + "end_offset": 10, + "type": "", + "position": 0, + "positionLength": 2 + }, + { + "token": "Open", + "start_offset": 0, + "end_offset": 4, + "type": "", + "position": 0 + }, + { + "token": "Search", + "start_offset": 4, + "end_offset": 10, + "type": "", + "position": 1 + }, + { + "token": "helped", + "start_offset": 11, + "end_offset": 17, + "type": "", + "position": 2 + }, + { + "token": "many", + "start_offset": 18, + "end_offset": 22, + "type": "", + "position": 3 + }, + { + "token": "employers", + "start_offset": 23, + "end_offset": 32, + "type": "", + "position": 4 + } + ] +} +``` From d01b4a4dc4bc35162f4eea46ca9474837570f574 Mon Sep 17 00:00:00 2001 From: AntonEliatra Date: Tue, 3 Dec 2024 14:39:27 +0000 Subject: [PATCH 21/69] add keyword repeat token filter docs #8149 (#8475) * add keyword repeat token filter docs #8149 Signed-off-by: Anton Rubin * Doc review Signed-off-by: Fanit Kolchina * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --------- Signed-off-by: Anton Rubin Signed-off-by: Fanit Kolchina Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Fanit Kolchina Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Nathan Bower --- _analyzers/token-filters/index.md | 2 +- _analyzers/token-filters/keyword-repeat.md | 160 +++++++++++++++++++++ 2 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 _analyzers/token-filters/keyword-repeat.md diff --git a/_analyzers/token-filters/index.md b/_analyzers/token-filters/index.md index a25acf6ec4..b9c52a6eae 100644 --- a/_analyzers/token-filters/index.md +++ b/_analyzers/token-filters/index.md @@ -35,7 +35,7 @@ Token filter | Underlying Lucene token filter| Description [`keep_types`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/keep-types/) | [TypeTokenFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/core/TypeTokenFilter.html) | Keeps or removes tokens of a specific type. [`keep_words`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/keep-words/) | [KeepWordFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/KeepWordFilter.html) | Checks the tokens against the specified word list and keeps only those that are in the list. [`keyword_marker`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/keyword-marker/) | [KeywordMarkerFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/KeywordMarkerFilter.html) | Marks specified tokens as keywords, preventing them from being stemmed. -`keyword_repeat` | [KeywordRepeatFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/KeywordRepeatFilter.html) | Emits each incoming token twice: once as a keyword and once as a non-keyword. +[`keyword_repeat`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/keyword-repeat/) | [KeywordRepeatFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/KeywordRepeatFilter.html) | Emits each incoming token twice: once as a keyword and once as a non-keyword. [`kstem`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/kstem/) | [KStemFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/en/KStemFilter.html) | Provides KStem-based stemming for the English language. Combines algorithmic stemming with a built-in dictionary. `kuromoji_completion` | [JapaneseCompletionFilter](https://lucene.apache.org/core/9_10_0/analysis/kuromoji/org/apache/lucene/analysis/ja/JapaneseCompletionFilter.html) | Adds Japanese romanized terms to the token stream (in addition to the original tokens). Usually used to support autocomplete on Japanese search terms. Note that the filter has a `mode` parameter, which should be set to `index` when used in an index analyzer and `query` when used in a search analyzer. Requires the `analysis-kuromoji` plugin. For information about installing the plugin, see [Additional plugins]({{site.url}}{{site.baseurl}}/install-and-configure/plugins/#additional-plugins). [`length`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/length/) | [LengthFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/LengthFilter.html) | Removes tokens that are shorter or longer than the length range specified by `min` and `max`. diff --git a/_analyzers/token-filters/keyword-repeat.md b/_analyzers/token-filters/keyword-repeat.md new file mode 100644 index 0000000000..5ba15a037c --- /dev/null +++ b/_analyzers/token-filters/keyword-repeat.md @@ -0,0 +1,160 @@ +--- +layout: default +title: Keyword repeat +parent: Token filters +nav_order: 210 +--- + +# Keyword repeat token filter + +The `keyword_repeat` token filter emits the keyword version of a token into a token stream. This filter is typically used when you want to retain both the original token and its modified version after further token transformations, such as stemming or synonym expansion. The duplicated tokens allow the original, unchanged version of the token to remain in the final analysis alongside the modified versions. + +The `keyword_repeat` token filter should be placed before stemming filters. Stemming is not applied to every token, thus you may have duplicate tokens in the same position after stemming. To remove duplicate tokens, use the `remove_duplicates` token filter after the stemmer. +{: .note} + + +## Example + +The following example request creates a new index named `my_index` and configures an analyzer with a `keyword_repeat` filter: + +```json +PUT /my_index +{ + "settings": { + "analysis": { + "filter": { + "my_kstem": { + "type": "kstem" + }, + "my_lowercase": { + "type": "lowercase" + } + }, + "analyzer": { + "my_custom_analyzer": { + "type": "custom", + "tokenizer": "standard", + "filter": [ + "my_lowercase", + "keyword_repeat", + "my_kstem" + ] + } + } + } + } +} +``` +{% include copy-curl.html %} + +## Generated tokens + +Use the following request to examine the tokens generated using the analyzer: + +```json +POST /my_index/_analyze +{ + "analyzer": "my_custom_analyzer", + "text": "Stopped quickly" +} +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + { + "token": "stopped", + "start_offset": 0, + "end_offset": 7, + "type": "", + "position": 0 + }, + { + "token": "stop", + "start_offset": 0, + "end_offset": 7, + "type": "", + "position": 0 + }, + { + "token": "quickly", + "start_offset": 8, + "end_offset": 15, + "type": "", + "position": 1 + }, + { + "token": "quick", + "start_offset": 8, + "end_offset": 15, + "type": "", + "position": 1 + } + ] +} +``` + +You can further examine the impact of the `keyword_repeat` token filter by adding the following parameters to the `_analyze` query: + +```json +POST /my_index/_analyze +{ + "analyzer": "my_custom_analyzer", + "text": "Stopped quickly", + "explain": true, + "attributes": "keyword" +} +``` +{% include copy-curl.html %} + +The response includes detailed information, such as tokenization, filtering, and the application of specific token filters: + +```json +{ + "detail": { + "custom_analyzer": true, + "charfilters": [], + "tokenizer": { + "name": "standard", + "tokens": [ + {"token": "OpenSearch","start_offset": 0,"end_offset": 10,"type": "","position": 0}, + {"token": "helped","start_offset": 11,"end_offset": 17,"type": "","position": 1}, + {"token": "many","start_offset": 18,"end_offset": 22,"type": "","position": 2}, + {"token": "employers","start_offset": 23,"end_offset": 32,"type": "","position": 3} + ] + }, + "tokenfilters": [ + { + "name": "lowercase", + "tokens": [ + {"token": "opensearch","start_offset": 0,"end_offset": 10,"type": "","position": 0}, + {"token": "helped","start_offset": 11,"end_offset": 17,"type": "","position": 1}, + {"token": "many","start_offset": 18,"end_offset": 22,"type": "","position": 2}, + {"token": "employers","start_offset": 23,"end_offset": 32,"type": "","position": 3} + ] + }, + { + "name": "keyword_marker_filter", + "tokens": [ + {"token": "opensearch","start_offset": 0,"end_offset": 10,"type": "","position": 0,"keyword": true}, + {"token": "helped","start_offset": 11,"end_offset": 17,"type": "","position": 1,"keyword": false}, + {"token": "many","start_offset": 18,"end_offset": 22,"type": "","position": 2,"keyword": false}, + {"token": "employers","start_offset": 23,"end_offset": 32,"type": "","position": 3,"keyword": false} + ] + }, + { + "name": "kstem_filter", + "tokens": [ + {"token": "opensearch","start_offset": 0,"end_offset": 10,"type": "","position": 0,"keyword": true}, + {"token": "help","start_offset": 11,"end_offset": 17,"type": "","position": 1,"keyword": false}, + {"token": "many","start_offset": 18,"end_offset": 22,"type": "","position": 2,"keyword": false}, + {"token": "employer","start_offset": 23,"end_offset": 32,"type": "","position": 3,"keyword": false} + ] + } + ] + } +} +``` \ No newline at end of file From d4799e47dbcd6f72d22ba341cfac1e47a463e575 Mon Sep 17 00:00:00 2001 From: AntonEliatra Date: Tue, 3 Dec 2024 14:45:03 +0000 Subject: [PATCH 22/69] add kuromoji_completion token filter docs #8151 (#8476) * add kuromoji_completion token filter docs #8151 Signed-off-by: Anton Rubin * Doc review Signed-off-by: Fanit Kolchina * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> * Update _analyzers/token-filters/kuromoji-completion.md Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --------- Signed-off-by: Anton Rubin Signed-off-by: Fanit Kolchina Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Fanit Kolchina Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Nathan Bower --- _analyzers/token-filters/index.md | 2 +- .../token-filters/kuromoji-completion.md | 127 ++++++++++++++++++ 2 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 _analyzers/token-filters/kuromoji-completion.md diff --git a/_analyzers/token-filters/index.md b/_analyzers/token-filters/index.md index b9c52a6eae..9184fa381c 100644 --- a/_analyzers/token-filters/index.md +++ b/_analyzers/token-filters/index.md @@ -37,7 +37,7 @@ Token filter | Underlying Lucene token filter| Description [`keyword_marker`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/keyword-marker/) | [KeywordMarkerFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/KeywordMarkerFilter.html) | Marks specified tokens as keywords, preventing them from being stemmed. [`keyword_repeat`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/keyword-repeat/) | [KeywordRepeatFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/KeywordRepeatFilter.html) | Emits each incoming token twice: once as a keyword and once as a non-keyword. [`kstem`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/kstem/) | [KStemFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/en/KStemFilter.html) | Provides KStem-based stemming for the English language. Combines algorithmic stemming with a built-in dictionary. -`kuromoji_completion` | [JapaneseCompletionFilter](https://lucene.apache.org/core/9_10_0/analysis/kuromoji/org/apache/lucene/analysis/ja/JapaneseCompletionFilter.html) | Adds Japanese romanized terms to the token stream (in addition to the original tokens). Usually used to support autocomplete on Japanese search terms. Note that the filter has a `mode` parameter, which should be set to `index` when used in an index analyzer and `query` when used in a search analyzer. Requires the `analysis-kuromoji` plugin. For information about installing the plugin, see [Additional plugins]({{site.url}}{{site.baseurl}}/install-and-configure/plugins/#additional-plugins). +[`kuromoji_completion`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/kuromoji-completion/) | [JapaneseCompletionFilter](https://lucene.apache.org/core/9_10_0/analysis/kuromoji/org/apache/lucene/analysis/ja/JapaneseCompletionFilter.html) | Adds Japanese romanized terms to a token stream (in addition to the original tokens). Usually used to support autocomplete of Japanese search terms. Note that the filter has a `mode` parameter that should be set to `index` when used in an index analyzer and `query` when used in a search analyzer. Requires the `analysis-kuromoji` plugin. For information about installing the plugin, see [Additional plugins]({{site.url}}{{site.baseurl}}/install-and-configure/plugins/#additional-plugins). [`length`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/length/) | [LengthFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/LengthFilter.html) | Removes tokens that are shorter or longer than the length range specified by `min` and `max`. [`limit`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/limit/) | [LimitTokenCountFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/LimitTokenCountFilter.html) | Limits the number of output tokens. For example, document field value sizes can be limited based on the token count. [`lowercase`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/lowercase/) | [LowerCaseFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/core/LowerCaseFilter.html) | Converts tokens to lowercase. The default [LowerCaseFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/core/LowerCaseFilter.html) processes the English language. To process other languages, set the `language` parameter to `greek` (uses [GreekLowerCaseFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/el/GreekLowerCaseFilter.html)), `irish` (uses [IrishLowerCaseFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/ga/IrishLowerCaseFilter.html)), or `turkish` (uses [TurkishLowerCaseFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/tr/TurkishLowerCaseFilter.html)). diff --git a/_analyzers/token-filters/kuromoji-completion.md b/_analyzers/token-filters/kuromoji-completion.md new file mode 100644 index 0000000000..24833e92e1 --- /dev/null +++ b/_analyzers/token-filters/kuromoji-completion.md @@ -0,0 +1,127 @@ +--- +layout: default +title: Kuromoji completion +parent: Token filters +nav_order: 230 +--- + +# Kuromoji completion token filter + +The `kuromoji_completion` token filter is used to stem Katakana words in Japanese, which are often used to represent foreign words or loanwords. This filter is especially useful for autocompletion or suggest queries, in which partial matches on Katakana words can be expanded to include their full forms. + +To use this token filter, you must first install the `analysis-kuromoji` plugin on all nodes by running `bin/opensearch-plugin install analysis-kuromoji` and then restart the cluster. For more information about installing additional plugins, see [Additional plugins]({{site.url}}{{site.baseurl}}/install-and-configure/additional-plugins/index/). + +## Example + +The following example request creates a new index named `kuromoji_sample` and configures an analyzer with a `kuromoji_completion` filter: + +```json +PUT kuromoji_sample +{ + "settings": { + "index": { + "analysis": { + "analyzer": { + "my_analyzer": { + "tokenizer": "kuromoji_tokenizer", + "filter": [ + "my_katakana_stemmer" + ] + } + }, + "filter": { + "my_katakana_stemmer": { + "type": "kuromoji_completion" + } + } + } + } + } +} +``` +{% include copy-curl.html %} + +## Generated tokens + +Use the following request to examine the tokens generated using the analyzer with text that translates to "use a computer": + +```json +POST /kuromoji_sample/_analyze +{ + "analyzer": "my_analyzer", + "text": "コンピューターを使う" +} +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + { + "token": "コンピューター", // The original Katakana word "computer". + "start_offset": 0, + "end_offset": 7, + "type": "word", + "position": 0 + }, + { + "token": "konpyuーtaー", // Romanized version (Romaji) of "コンピューター". + "start_offset": 0, + "end_offset": 7, + "type": "word", + "position": 0 + }, + { + "token": "konnpyuーtaー", // Another possible romanized version of "コンピューター" (with a slight variation in the spelling). + "start_offset": 0, + "end_offset": 7, + "type": "word", + "position": 0 + }, + { + "token": "を", // A Japanese particle, "wo" or "o" + "start_offset": 7, + "end_offset": 8, + "type": "word", + "position": 1 + }, + { + "token": "wo", // Romanized form of the particle "を" (often pronounced as "o"). + "start_offset": 7, + "end_offset": 8, + "type": "word", + "position": 1 + }, + { + "token": "o", // Another version of the romanization. + "start_offset": 7, + "end_offset": 8, + "type": "word", + "position": 1 + }, + { + "token": "使う", // The verb "use" in Kanji. + "start_offset": 8, + "end_offset": 10, + "type": "word", + "position": 2 + }, + { + "token": "tukau", // Romanized version of "使う" + "start_offset": 8, + "end_offset": 10, + "type": "word", + "position": 2 + }, + { + "token": "tsukau", // Another romanized version of "使う", where "tsu" is more phonetically correct + "start_offset": 8, + "end_offset": 10, + "type": "word", + "position": 2 + } + ] +} +``` \ No newline at end of file From f181085031050eab7520bfe59c78700311f3d3ca Mon Sep 17 00:00:00 2001 From: Christian Franchin Date: Tue, 3 Dec 2024 11:55:03 -0300 Subject: [PATCH 23/69] Update workers parameter description with 1-10 range (#8826) * Update workers parameter description with 1-10 range Signed-off-by: Christian Franchin * Update _data-prepper/pipelines/configuration/sources/s3.md Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Signed-off-by: Christian Franchin --------- Signed-off-by: Christian Franchin Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --- _data-prepper/pipelines/configuration/sources/s3.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_data-prepper/pipelines/configuration/sources/s3.md b/_data-prepper/pipelines/configuration/sources/s3.md index db92718a36..7ca27ee500 100644 --- a/_data-prepper/pipelines/configuration/sources/s3.md +++ b/_data-prepper/pipelines/configuration/sources/s3.md @@ -104,7 +104,7 @@ Option | Required | Type | Description `s3_select` | No | [s3_select](#s3_select) | The Amazon S3 Select configuration. `scan` | No | [scan](#scan) | The S3 scan configuration. `delete_s3_objects_on_read` | No | Boolean | When `true`, the S3 scan attempts to delete S3 objects after all events from the S3 object are successfully acknowledged by all sinks. `acknowledgments` should be enabled when deleting S3 objects. Default is `false`. -`workers` | No | Integer | Configures the number of worker threads that the source uses to read data from S3. Leave this value as the default unless your S3 objects are less than 1 MB in size. Performance may decrease for larger S3 objects. This setting affects SQS-based sources and S3-Scan sources. Default is `1`. +`workers` | No | Integer | Configures the number of worker threads (1--10) that the source uses to read data from S3. Leave this value as the default unless your S3 objects are less than 1 MB in size. Performance may decrease for larger S3 objects. This setting affects SQS-based sources and S3-Scan sources. Default is `1`. From 91e311ef12e4f59b5a1962d70bfddda2b44aa5a4 Mon Sep 17 00:00:00 2001 From: Michael Oviedo Date: Tue, 3 Dec 2024 07:51:13 -0800 Subject: [PATCH 24/69] update aggregate command reference with latest changes (#8823) * update aggregate command reference with latest changes Signed-off-by: Michael Oviedo * Update aggregate.md Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> --------- Signed-off-by: Michael Oviedo Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> Co-authored-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> --- _benchmark/reference/commands/aggregate.md | 23 +++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/_benchmark/reference/commands/aggregate.md b/_benchmark/reference/commands/aggregate.md index 17612f1164..a891bf3edf 100644 --- a/_benchmark/reference/commands/aggregate.md +++ b/_benchmark/reference/commands/aggregate.md @@ -69,9 +69,30 @@ Aggregate test execution ID: aggregate_results_geonames_9aafcfb8-d3b7-4583-864e ------------------------------- ``` -The results will be aggregated into one test execution and stored under the ID shown in the output: +The results will be aggregated into one test execution and stored under the ID shown in the output. +### Additional options - `--test-execution-id`: Define a unique ID for the aggregated test execution. - `--results-file`: Write the aggregated results to the provided file. - `--workload-repository`: Define the repository from which OpenSearch Benchmark will load workloads (default is `default`). +## Aggregated results + +Aggregated results includes the following information: + +- **Relative Standard Deviation (RSD)**: For each metric an additional `mean_rsd` value shows the spread of results across test executions. +- **Overall min/max values**: Instead of averaging minimum and maximum values, the aggregated result include `overall_min` and `overall_max` which reflect the true minimum/maximum across all test runs. +- **Storage**: Aggregated test results are stored in a separate `aggregated_results` folder alongside the `test_executions` folder. + +The following example shows aggregated results: + +```json + "throughput": { + "overall_min": 29056.890292903263, + "mean": 50115.8603858536, + "median": 50099.54349684457, + "overall_max": 72255.15946248993, + "unit": "docs/s", + "mean_rsd": 59.426059705973664 + }, +``` From 9bd4c42c4a52a10d87ac893eaa00c95b6d5ef3f0 Mon Sep 17 00:00:00 2001 From: Krishna Kondaka Date: Tue, 3 Dec 2024 08:04:25 -0800 Subject: [PATCH 25/69] Update expression and anomaly detector documentation (#8041) * Update expression and anomaly detector documentation Signed-off-by: Kondaka * Update _data-prepper/pipelines/expression-syntax.md Co-authored-by: David Venable Signed-off-by: Melissa Vagi * Update _data-prepper/pipelines/expression-syntax.md Co-authored-by: David Venable Signed-off-by: Melissa Vagi * Update _data-prepper/pipelines/expression-syntax.md Co-authored-by: David Venable Signed-off-by: Melissa Vagi * Update _data-prepper/pipelines/expression-syntax.md Co-authored-by: David Venable Signed-off-by: Melissa Vagi * Update _data-prepper/pipelines/expression-syntax.md Co-authored-by: David Venable Signed-off-by: Melissa Vagi * Apply suggestions from code review Co-authored-by: Melissa Vagi Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> --------- Signed-off-by: Kondaka Signed-off-by: Melissa Vagi Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> Co-authored-by: Melissa Vagi Co-authored-by: David Venable Co-authored-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> --- .../processors/anomaly-detector.md | 1 + _data-prepper/pipelines/expression-syntax.md | 64 ++++++++++++++++++- 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/_data-prepper/pipelines/configuration/processors/anomaly-detector.md b/_data-prepper/pipelines/configuration/processors/anomaly-detector.md index 9628bb6caf..ba574bdf7d 100644 --- a/_data-prepper/pipelines/configuration/processors/anomaly-detector.md +++ b/_data-prepper/pipelines/configuration/processors/anomaly-detector.md @@ -53,6 +53,7 @@ You can configure `random_cut_forest` mode with the following options. | `sample_size` | `256` | 100--2500 | The sample size used in the ML algorithm. | | `time_decay` | `0.1` | 0--1.0 | The time decay value used in the ML algorithm. Used as the mathematical expression `timeDecay` divided by `SampleSize` in the ML algorithm. | | `type` | `metrics` | N/A | The type of data sent to the algorithm. | +| `output_after` | 32 | N/A | Specifies the number of events to process before outputting any detected anomalies. | | `version` | `1.0` | N/A | The algorithm version number. | ## Usage diff --git a/_data-prepper/pipelines/expression-syntax.md b/_data-prepper/pipelines/expression-syntax.md index 383b54c19b..07f68ee58e 100644 --- a/_data-prepper/pipelines/expression-syntax.md +++ b/_data-prepper/pipelines/expression-syntax.md @@ -30,6 +30,9 @@ The following table lists the supported operators. Operators are listed in order |----------------------|-------------------------------------------------------|---------------| | `()` | Priority expression | Left to right | | `not`
`+`
`-`| Unary logical NOT
Unary positive
Unary negative | Right to left | +| `*`, `/` | Multiplication and division operators | Left to right | +| `+`, `-` | Addition and subtraction operators | Left to right | +| `+` | String concatenation operator | Left to right | | `<`, `<=`, `>`, `>=` | Relational operators | Left to right | | `==`, `!=` | Equality operators | Left to right | | `and`, `or` | Conditional expression | Left to right | @@ -78,7 +81,6 @@ Conditional expressions allow you to combine multiple expressions or values usin or not ``` -{% include copy-curl.html %} The following are some example conditional expressions: @@ -91,9 +93,64 @@ not /status_code in {200, 202} ``` {% include copy-curl.html %} +### Arithmetic expressions + +Arithmetic expressions enable basic mathematical operations like addition, subtraction, multiplication, and division. These expressions can be combined with conditional expressions to create more complex conditional statements. The available arithmetic operators are +, -, *, and /. The syntax for using the arithmetic operators is as follows: + +``` + + + - + * + / +``` + +The following are example arithmetic expressions: + +``` +/value + length(/message) +/bytes / 1024 +/value1 - /value2 +/TimeInSeconds * 1000 +``` +{% include copy-curl.html %} + +The following are some example arithmetic expressions used in conditional expressions : + +``` +/value + length(/message) > 200 +/bytes / 1024 < 10 +/value1 - /value2 != /value3 + /value4 +``` +{% include copy-curl.html %} + +### String concatenation expressions + +String concatenation expressions enable you to combine strings to create new strings. These concatenated strings can also be used within conditional expressions. The syntax for using string concatenation is as follows: + +``` + + +``` + +The following are example string concatenation expressions: + +``` +/name + "suffix" +"prefix" + /name +"time of " + /timeInMs + " ms" +``` +{% include copy-curl.html %} + +The following are example string concatenation expressions that can be used in conditional expressions: + +``` +/service + ".com" == /url +"www." + /service != /url +``` +{% include copy-curl.html %} + ### Reserved symbols -Reserved symbols are symbols that are not currently used in the expression syntax but are reserved for possible future functionality or extensions. Reserved symbols include `^`, `*`, `/`, `%`, `+`, `-`, `xor`, `=`, `+=`, `-=`, `*=`, `/=`, `%=`, `++`, `--`, and `${}`. +Certain symbols, such as ^, %, xor, =, +=, -=, *=, /=, %=, ++, --, and ${}, are reserved for future functionality or extensions. Reserved symbols include `^`, `%`, `xor`, `=`, `+=`, `-=`, `*=`, `/=`, `%=`, `++`, `--`, and `${}`. ## Syntax components @@ -170,6 +227,9 @@ White space is optional around relational operators, regex equality operators, e | `()` | Priority expression | Yes | `/a==(/b==200)`
`/a in ({200})` | `/status in({200})` | | `in`, `not in` | Set operators | Yes | `/a in {200}`
`/a not in {400}` | `/a in{200, 202}`
`/a not in{400}` | | `<`, `<=`, `>`, `>=` | Relational operators | No | `/status < 300`
`/status>=300` | | +| `+` | String concatenation operator | No | `/status_code + /message + "suffix"` +| `+`, `-` | Arithmetic addition and subtraction operators | No | `/status_code + length(/message) - 2` +| `*`, `/` | Multiplication and division operators | No | `/status_code * length(/message) / 3` | `=~`, `!~` | Regex equality operators | No | `/msg =~ "^\w*$"`
`/msg=~"^\w*$"` | | | `==`, `!=` | Equality operators | No | `/status == 200`
`/status_code==200` | | | `and`, `or`, `not` | Conditional operators | Yes | `/a<300 and /b>200` | `/b<300and/b>200` | From 064a2b56dff900a9d94407ac10e22559ea60f7e4 Mon Sep 17 00:00:00 2001 From: AntonEliatra Date: Tue, 3 Dec 2024 16:06:21 +0000 Subject: [PATCH 26/69] add unique token filter docs #8451 (#8463) * add unique token filter docs #8451 Signed-off-by: Anton Rubin * updating parameter table Signed-off-by: Anton Rubin * Doc review Signed-off-by: Fanit Kolchina * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --------- Signed-off-by: Anton Rubin Signed-off-by: Fanit Kolchina Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Fanit Kolchina Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Nathan Bower --- _analyzers/token-filters/index.md | 2 +- _analyzers/token-filters/unique.md | 106 +++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 _analyzers/token-filters/unique.md diff --git a/_analyzers/token-filters/index.md b/_analyzers/token-filters/index.md index 9184fa381c..96efdeae15 100644 --- a/_analyzers/token-filters/index.md +++ b/_analyzers/token-filters/index.md @@ -61,7 +61,7 @@ Token filter | Underlying Lucene token filter| Description [`synonym_graph`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/synonym-graph/) | N/A | Supplies a synonym list, including multiword synonyms, for the analysis process. [`trim`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/trim/) | [TrimFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/TrimFilter.html) | Trims leading and trailing white space characters from each token in a stream. [`truncate`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/truncate/) | [TruncateTokenFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/TruncateTokenFilter.html) | Truncates tokens with lengths exceeding the specified character limit. -`unique` | N/A | Ensures each token is unique by removing duplicate tokens from a stream. +[`unique`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/unique/) | N/A | Ensures that each token is unique by removing duplicate tokens from a stream. [`uppercase`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/uppercase/) | [UpperCaseFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/core/LowerCaseFilter.html) | Converts tokens to uppercase. `word_delimiter` | [WordDelimiterFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/WordDelimiterFilter.html) | Splits tokens at non-alphanumeric characters and performs normalization based on the specified rules. [`word_delimiter_graph`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/word-delimiter-graph/) | [WordDelimiterGraphFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/WordDelimiterGraphFilter.html) | Splits tokens at non-alphanumeric characters and performs normalization based on the specified rules. Assigns a `positionLength` attribute to multi-position tokens. diff --git a/_analyzers/token-filters/unique.md b/_analyzers/token-filters/unique.md new file mode 100644 index 0000000000..c4dfcbab16 --- /dev/null +++ b/_analyzers/token-filters/unique.md @@ -0,0 +1,106 @@ +--- +layout: default +title: Unique +parent: Token filters +nav_order: 450 +--- + +# Unique token filter + +The `unique` token filter ensures that only unique tokens are kept during the analysis process, removing duplicate tokens that appear within a single field or text block. + +## Parameters + +The `unique` token filter can be configured with the following parameter. + +Parameter | Required/Optional | Data type | Description +:--- | :--- | :--- | :--- +`only_on_same_position` | Optional | Boolean | If `true`, the token filter acts as a `remove_duplicates` token filter and only removes tokens that are in the same position. Default is `false`. + +## Example + +The following example request creates a new index named `unique_example` and configures an analyzer with a `unique` filter: + +```json +PUT /unique_example +{ + "settings": { + "analysis": { + "filter": { + "unique_filter": { + "type": "unique", + "only_on_same_position": false + } + }, + "analyzer": { + "unique_analyzer": { + "type": "custom", + "tokenizer": "standard", + "filter": [ + "lowercase", + "unique_filter" + ] + } + } + } + } +} +``` +{% include copy-curl.html %} + +## Generated tokens + +Use the following request to examine the tokens generated using the analyzer: + +```json +GET /unique_example/_analyze +{ + "analyzer": "unique_analyzer", + "text": "OpenSearch OpenSearch is powerful powerful and scalable" +} +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + { + "token": "opensearch", + "start_offset": 0, + "end_offset": 10, + "type": "", + "position": 0 + }, + { + "token": "is", + "start_offset": 22, + "end_offset": 24, + "type": "", + "position": 1 + }, + { + "token": "powerful", + "start_offset": 25, + "end_offset": 33, + "type": "", + "position": 2 + }, + { + "token": "and", + "start_offset": 43, + "end_offset": 46, + "type": "", + "position": 3 + }, + { + "token": "scalable", + "start_offset": 47, + "end_offset": 55, + "type": "", + "position": 4 + } + ] +} +``` From e31a7e949ccd9631a7569e10d4378ab2a86bf5d2 Mon Sep 17 00:00:00 2001 From: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> Date: Tue, 3 Dec 2024 12:20:54 -0600 Subject: [PATCH 27/69] Update flat-object.md (#8863) Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> --- _field-types/supported-field-types/flat-object.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/_field-types/supported-field-types/flat-object.md b/_field-types/supported-field-types/flat-object.md index c9e59710e1..65d7c6dc8e 100644 --- a/_field-types/supported-field-types/flat-object.md +++ b/_field-types/supported-field-types/flat-object.md @@ -56,7 +56,8 @@ The flat object field type supports the following queries: - [Multi-match]({{site.url}}{{site.baseurl}}/query-dsl/full-text/multi-match/) - [Query string]({{site.url}}{{site.baseurl}}/query-dsl/full-text/query-string/) - [Simple query string]({{site.url}}{{site.baseurl}}/query-dsl/full-text/simple-query-string/) -- [Exists]({{site.url}}{{site.baseurl}}/query-dsl/term/exists/) +- [Exists]({{site.url}}{{site.baseurl}}/query-dsl/term/exists/) +- [Wildcard]({{site.url}}{{site.baseurl}}/query-dsl/term/wildcard/) ## Limitations @@ -243,4 +244,4 @@ PUT /test-index/ ``` {% include copy-curl.html %} -Because `issue.number` is not part of the flat object, you can use it to aggregate and sort documents. \ No newline at end of file +Because `issue.number` is not part of the flat object, you can use it to aggregate and sort documents. From 1fcf2785a8db8d67e7584f359d52e78e0ce96810 Mon Sep 17 00:00:00 2001 From: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> Date: Tue, 3 Dec 2024 13:34:27 -0600 Subject: [PATCH 28/69] Add migration phases pages (#8828) * Add first three migration phases pages Signed-off-by: Archer * Apply suggestions from code review Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Add backfill page. Signed-off-by: Archer * Update backfill.md Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Add replayer page. Signed-off-by: Archer * Fix grammar. Signed-off-by: Archer * Add final migration phases page. Signed-off-by: Archer * Apply suggestions from code review Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Apply suggestions from code review Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Update _migrations/migration-phases/verifying-tools-for-migration.md Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Update _migrations/migration-phases/backfill.md Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Add migration phase links Signed-off-by: Archer * Edit migration console section Signed-off-by: Archer * Apply suggestions from code review Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Update migrating-metadata.md Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Apply suggestions from code review Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Rename traffic replacer. Signed-off-by: Archer * Update _migrations/migration-phases/verifying-tools-for-migration.md Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Add sentence about live traffic capture Signed-off-by: Archer * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Update _migrations/migration-phases/backfill.md Co-authored-by: Nathan Bower Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Update _migrations/migration-phases/verifying-tools-for-migration.md Co-authored-by: Nathan Bower Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Update _migrations/migration-phases/verifying-tools-for-migration.md Co-authored-by: Nathan Bower Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Add editorial review. Signed-off-by: Archer * Additional editorial comments. Signed-off-by: Archer * Editorial for infra and traffic Signed-off-by: Archer * Editorial comments for using traffic replayer. Signed-off-by: Archer * Add final editorial comments. Signed-off-by: Archer * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> --------- Signed-off-by: Archer Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> Co-authored-by: Nathan Bower --- _migrations/getting-started-data-migration.md | 7 +- .../accessing-the-migration-console.md | 29 +- _migrations/migration-console/index.md | 7 +- .../migration-console-commands-references.md | 77 +++-- .../assessing-your-cluster-for-migration.md | 44 +++ .../migration-phases/assessment/index.md | 7 - .../assessment/required-client-changes.md | 41 --- .../understanding-breaking-changes.md | 16 - _migrations/migration-phases/backfill.md | 174 ++++++++++ .../backfill/backfill-execution.md | 94 ------ .../backfill/backfill-result-validation.md | 37 --- .../capture-proxy-data-replication.md | 45 --- .../migration-phases/backfill/index.md | 7 - ...Switching-Traffic-from-Source-to-Target.md | 37 --- .../client-traffic-switchover/index.md | 7 - _migrations/migration-phases/index.md | 9 +- .../metadata/Snapshot-Creation.md | 56 ---- .../migration-phases/metadata/index.md | 7 - ...ata-Migration.md => migrating-metadata.md} | 95 ++++-- .../post-migration-cleanup/index.md | 7 - ...d => removing-migration-infrastructure.md} | 14 +- .../In-flight-Validation.md | 152 --------- .../Synchronized-Cluster-Validation.md | 161 --------- .../replay-activation-and-validation/index.md | 7 - .../Client-Traffic-Switchover-Verification.md | 23 -- .../Snapshot-Creation-Verification.md | 100 ------ .../System-Reset-Before-Migration.md | 22 -- .../Traffic-Capture-Verification.md | 36 -- .../setup-verification/index.md | 7 - ...itching-traffic-from-the-source-cluster.md | 52 +++ .../using-traffic-replayer.md | 307 ++++++++++++++++++ .../verifying-migration-tools.md | 196 +++++++++++ 32 files changed, 923 insertions(+), 957 deletions(-) create mode 100644 _migrations/migration-phases/assessing-your-cluster-for-migration.md delete mode 100644 _migrations/migration-phases/assessment/index.md delete mode 100644 _migrations/migration-phases/assessment/required-client-changes.md delete mode 100644 _migrations/migration-phases/assessment/understanding-breaking-changes.md create mode 100644 _migrations/migration-phases/backfill.md delete mode 100644 _migrations/migration-phases/backfill/backfill-execution.md delete mode 100644 _migrations/migration-phases/backfill/backfill-result-validation.md delete mode 100644 _migrations/migration-phases/backfill/capture-proxy-data-replication.md delete mode 100644 _migrations/migration-phases/backfill/index.md delete mode 100644 _migrations/migration-phases/client-traffic-switchover/Switching-Traffic-from-Source-to-Target.md delete mode 100644 _migrations/migration-phases/client-traffic-switchover/index.md delete mode 100644 _migrations/migration-phases/metadata/Snapshot-Creation.md delete mode 100644 _migrations/migration-phases/metadata/index.md rename _migrations/migration-phases/{metadata/Metadata-Migration.md => migrating-metadata.md} (61%) delete mode 100644 _migrations/migration-phases/post-migration-cleanup/index.md rename _migrations/migration-phases/{post-migration-cleanup/Migration-Infrastructure-Teardown.md => removing-migration-infrastructure.md} (60%) delete mode 100644 _migrations/migration-phases/replay-activation-and-validation/In-flight-Validation.md delete mode 100644 _migrations/migration-phases/replay-activation-and-validation/Synchronized-Cluster-Validation.md delete mode 100644 _migrations/migration-phases/replay-activation-and-validation/index.md delete mode 100644 _migrations/migration-phases/setup-verification/Client-Traffic-Switchover-Verification.md delete mode 100644 _migrations/migration-phases/setup-verification/Snapshot-Creation-Verification.md delete mode 100644 _migrations/migration-phases/setup-verification/System-Reset-Before-Migration.md delete mode 100644 _migrations/migration-phases/setup-verification/Traffic-Capture-Verification.md delete mode 100644 _migrations/migration-phases/setup-verification/index.md create mode 100644 _migrations/migration-phases/switching-traffic-from-the-source-cluster.md create mode 100644 _migrations/migration-phases/using-traffic-replayer.md create mode 100644 _migrations/migration-phases/verifying-migration-tools.md diff --git a/_migrations/getting-started-data-migration.md b/_migrations/getting-started-data-migration.md index 8ae1a7f457..b788522d5a 100644 --- a/_migrations/getting-started-data-migration.md +++ b/_migrations/getting-started-data-migration.md @@ -181,7 +181,7 @@ To deploy Migration Assistant, use the following steps: These commands deploy the following stacks: * Migration Assistant network stack -* Reindex From Snapshot stack +* `Reindex-from-snapshot` stack * Migration console stack --- @@ -253,7 +253,7 @@ Run the following command to migrate metadata: console metadata migrate [...] ``` -For more information, see [Metadata migration]. +For more information, see [Migrating metadata]({{site.url}}{{site.baseurl}}/migrations/migration-phases/migrating-metadata/). --- @@ -285,7 +285,7 @@ You can now use RFS to migrate documents from your original cluster: console backfill stop ``` -For more information, see [Backfill execution]. +For more information, see [Backfill]({{site.url}}{{site.baseurl}}/migrations/migration-phases/backfill/). --- @@ -328,4 +328,3 @@ fields @message If any failed documents are identified, you can index the failed documents directly as opposed to using RFS. -For more information, see [Backfill migration]. diff --git a/_migrations/migration-console/accessing-the-migration-console.md b/_migrations/migration-console/accessing-the-migration-console.md index 9c09515ab6..d6cf9ec150 100644 --- a/_migrations/migration-console/accessing-the-migration-console.md +++ b/_migrations/migration-console/accessing-the-migration-console.md @@ -1,12 +1,15 @@ +--- +layout: default +title: Accessing the migration console +nav_order: 35 +parent: Migration console +--- +# Accessing the migration console +The Bootstrap box deployed through Migration Assistant contains a script that simplifies access to the migration console through that instance. -The Migrations Assistant deployment includes an ECS task that hosts tools to run different phases of the migration and check the progress or results of the migration. - -## SSH into the Migration Console -Following the AWS Solutions deployment, the bootstrap box contains a script that simplifies access to the migration console through that instance. - -To access the Migration Console, use the following commands: +To access the migration console, use the following commands: ```shell export STAGE=dev @@ -16,13 +19,7 @@ export AWS_REGION=us-west-2 When opening the console a message will appear above the command prompt, `Welcome to the Migration Assistant Console`. -
- - -SSH from any machine into Migration Console - - -On a machine with the [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) ↗ and the [AWS Session Manager Plugin](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html) ↗, you can directly connect to the migration console. Ensure you've run `aws configure` with credentials that have access to the environment. +On a machine with the [AWS Command Line Interface (AWS CLI)](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) and the [AWS Session Manager plugin](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html), you can directly connect to the migration console. Ensure that you've run `aws configure` with credentials that have access to the environment. Use the following commands: @@ -32,10 +29,6 @@ export SERVICE_NAME=migration-console export TASK_ARN=$(aws ecs list-tasks --cluster migration-${STAGE}-ecs-cluster --family "migration-${STAGE}-${SERVICE_NAME}" | jq --raw-output '.taskArns[0]') aws ecs execute-command --cluster "migration-${STAGE}-ecs-cluster" --task "${TASK_ARN}" --container "${SERVICE_NAME}" --interactive --command "/bin/bash" ``` -
- -## Troubleshooting -### Deployment Stage -Typically, `STAGE` is `dev`, but this may vary based on what the user specified during deployment. \ No newline at end of file +Typically, `STAGE` is equivalent to a standard `dev` environment, but this may vary based on what the user specified during deployment. \ No newline at end of file diff --git a/_migrations/migration-console/index.md b/_migrations/migration-console/index.md index 78a8011b57..7ebac65836 100644 --- a/_migrations/migration-console/index.md +++ b/_migrations/migration-console/index.md @@ -3,4 +3,9 @@ layout: default title: Migration console nav_order: 30 has_children: true ---- \ No newline at end of file +--- + +The Migrations Assistant deployment includes an Amazon Elastic Container Service (Amazon ECS) task that hosts tools that run different phases of the migration and check the progress or results of the migration. This ECS task is called the **migration console**. The migration console is a command line interface used to interact with the deployed components of the solution. + +This section provides information about how to access the migration console and what commands are supported. + diff --git a/_migrations/migration-console/migration-console-commands-references.md b/_migrations/migration-console/migration-console-commands-references.md index 8b906ff9b2..55731229e0 100644 --- a/_migrations/migration-console/migration-console-commands-references.md +++ b/_migrations/migration-console/migration-console-commands-references.md @@ -1,83 +1,106 @@ +--- +layout: default +title: Command reference +nav_order: 35 +parent: Migration console +--- +# Migration console command reference -The Migration Assistant Console is a command line interface to interact with the deployed components of the solution. +Migration console commands follow this syntax: `console [component] [action]`. The components include `clusters`, `backfill`, `snapshot`, `metadata`, and `replay`. The console is configured with a registry of the deployed services and the source and target cluster, generated from the `cdk.context.json` values. -The commands are in the form of `console [component] [action]`. The components include `clusters`, `backfill` (e.g the Reindex from Snapshot service), `snapshot`, `metadata`, `replay`, etc. The console is configured with a registry of the deployed services and the source and target cluster, generated from the `cdk.context.json` values. - -## Commonly Used Commands +## Commonly used commands The exact commands used will depend heavily on use-case and goals, but the following are a series of common commands with a quick description of what they do. +### Check connection + +Reports whether both the source and target clusters can be reached and provides their versions. + ```sh console clusters connection-check ``` -Reports whether both the source and target clusters can be reached and their versions. +### Run `cat-indices` + +Runs the `cat-indices` API on the cluster. ```sh console clusters cat-indices ``` -Runs the `_cat/indices` command on each cluster and prints the results. -*** +### Create a snapshot + +Creates a snapshot of the source cluster and stores it in a preconfigured Amazon Simple Storage Service (Amazon S3) bucket. ```sh console snapshot create ``` -Initiates creating a snapshot on the source cluster, into a pre-configured S3 bucket. + +## Check snapshot status + +Runs a detailed check on the snapshot creation status, including estimated completion time: ```sh console snapshot status --deep-check ``` -Runs a detailed check on the status of the snapshot creation, including estimated completion time. -*** +## Evaluate metadata + +Performs a dry run of metadata migration, showing which indexes, templates, and other objects will be migrated to the target cluster. ```sh console metadata evaluate ``` -Perform a dry run of metadata migration, showing which indices, templates, and other objects will be migrated to the target cluster. + +## Migrate metadata + +Migrates the metadata from the source cluster to the target cluster. ```sh console metadata migrate ``` -Perform an actual metadata migration. -*** +## Start a backfill -```sh -console backfill start -``` -If the Reindex From Snapshot service is enabled, start an instance of the service to begin moving documents to the target cluster. +If `Reindex-From-Snapshot` (RFS) is enabled, this command starts an instance of the service to begin moving documents to the target cluster: There are similar `scale UNITS` and `stop` commands to change the number of active instances for RFS. + ```sh -console backfill status --deep-check +console backfill start ``` -See the current status of the backfill migration, with the number of instances operating and the progress of the shards. -*** +## Check backfill status + +Gets the current status of the backfill migration, including the number of operating instances and the progress of the shards. + + +## Start Traffic Replayer + +If Traffic Replayer is enabled, this command starts an instance of Traffic Replayer to begin replaying traffic against the target cluster. +The `stop` command stops all active instances. ```sh console replay start ``` -If the Traffic Replayer service is enabled, start an instance of the service to begin replaying traffic against the target cluster. -The `stop` command stops all active instances. -*** +## Read logs + +Reads any logs that exist when running Traffic Replayer. Use tab completion on the path to fill in the available `NODE_IDs` and, if applicable, log file names. The tuple logs roll over at a certain size threshold, so there may be many files named with timestamps. The `jq` command pretty-prints each line of the tuple output before writing it to file. ```sh console tuples show --in /shared-logs-output/traffic-replayer-default/[NODE_ID]/tuples/console.log | jq > readable_tuples.json ``` -Use tab completion on the path to fill in the available node ids and, if applicable, log file names. The tuples logs roll over at a certain size threshold, so there may be many files named with timestamps. The `jq` command pretty-prints each line of the tuple output before writing it to file. -## Command Reference -All commands and options can be explored within the tool itself by using the `--help` option, either for the entire `console` application or for individual components (e.g. `console backfill --help`). The console also has command autocomplete set up to assist with usage. +## Help command -``` +All commands and options can be explored within the tool itself by using the `--help` option, either for the entire `console` application or for individual components (for example, `console backfill --help`). For example: + +```bash $ console --help Usage: console [OPTIONS] COMMAND [ARGS]... diff --git a/_migrations/migration-phases/assessing-your-cluster-for-migration.md b/_migrations/migration-phases/assessing-your-cluster-for-migration.md new file mode 100644 index 0000000000..d056754555 --- /dev/null +++ b/_migrations/migration-phases/assessing-your-cluster-for-migration.md @@ -0,0 +1,44 @@ +--- +layout: default +title: Assessing your cluster for migration +nav_order: 60 +has_children: true +parent: Migration phases +--- + +# Assessing your cluster for migration + +The goal of Migration Assistant is to streamline the process of migrating from one location or version of Elasticsearch/OpenSearch to another. However, completing a migration sometimes requires resolving client compatibility issues before they can communicate directly with the target cluster. + +## Understanding breaking changes + +Before performing any upgrade or migration, you should review any breaking changes documentation. Even if the cluster is migrated, there may be changes required in order for clients to connect to the new cluster. + +## Upgrade and breaking changes guides + +For migration paths between Elasticsearch 6.8 and OpenSearch 2.x, you should be familiar with the following documentation, depending on your specific use case: + +* [Upgrading Amazon OpenSearch Service domains](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/version-migration.html). + +* [Amazon OpenSearch Service rename - Summary of changes](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/rename.html). + +* [OpenSearch breaking changes](https://opensearch.org/docs/latest/breaking-changes/). + +The next step is to set up a proper test bed to verify that your applications will work as expected on the target version. + +## Impact of data transformations + +Any time you apply a transformation to your data, such as changing index names, modifying field names or field mappings, or splitting indexes with type mappings, these changes may need to be reflected in your client configurations. For example, if your clients are reliant on specific index or field names, you must ensure that their queries are updated accordingly. + + + +We recommend running production-like queries against the target cluster before switching to actual production traffic. This helps verify that the client can: + +- Communicate with the target cluster. +- Locate the necessary indexes and fields. +- Retrieve the expected results. + +For complex migrations involving multiple transformations or breaking changes, we highly recommend performing a trial migration with representative, non-production data (for example, in a staging environment) to fully test client compatibility with the target cluster. + + + diff --git a/_migrations/migration-phases/assessment/index.md b/_migrations/migration-phases/assessment/index.md deleted file mode 100644 index feda45c3f7..0000000000 --- a/_migrations/migration-phases/assessment/index.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -layout: default -title: Assessing your cluster -nav_order: 60 -has_children: true -parent: Migration phases ---- \ No newline at end of file diff --git a/_migrations/migration-phases/assessment/required-client-changes.md b/_migrations/migration-phases/assessment/required-client-changes.md deleted file mode 100644 index 0cce4624bf..0000000000 --- a/_migrations/migration-phases/assessment/required-client-changes.md +++ /dev/null @@ -1,41 +0,0 @@ - - - -The goal of the Migration Assistant is to streamline the process of migrating from one location or version of Elasticsearch/OpenSearch to another. However, completing a migration sometimes requires resolving client compatibility issues before they can communicate directly with the target cluster. - -It's crucial to understand and plan for any necessary changes before beginning the migration process. The previous page on [[breaking changes between versions|Understanding-breaking-changes]] is a useful resource for identifying potential issues. - -## Data Transformations and Client Impact - -Any time you apply a transformation to your data, such as: - -- Changing index names -- Modifying field names or field mappings -- Splitting indices with type mappings - -These changes may need to be reflected in your client configurations. For example, if your clients are reliant on specific index or field names, you must ensure that their queries are updated accordingly. - -We recommend running production-like queries against the target cluster before switching over actual production traffic. This helps verify that the client can: - -- Communicate with the target cluster -- Locate the necessary indices and fields -- Retrieve the expected results - -For complex migrations involving multiple transformations or breaking changes, we highly recommend performing a trial migration with representative, non-production data (e.g., in a staging environment) to fully test client compatibility with the target cluster. - -## Troubleshooting - -### Migrating from Elasticsearch (Post-Fork) to OpenSearch - -Migrating from post-fork Elasticsearch (7.10.2+) to OpenSearch presents additional challenges because some Elasticsearch clients include license or version checks that can artificially break compatibility. - -No post-fork Elasticsearch clients are fully compatible with OpenSearch 2.x. We recommend switching to the latest version of the [OpenSearch Clients](https://opensearch.org/docs/latest/clients/) ↗. - -### Inspecting the tuple output - -The Replayer outputs that show the exact requests being sent to both the source and target clusters. Examining these tuples can help you identify any transformations between requests, allowing you to ensure that these changes are reflected in your client code. See [[In-flight Validation]] for details. - -### Related Links - -For more information about OpenSearch clients, refer to the official documentation: - diff --git a/_migrations/migration-phases/assessment/understanding-breaking-changes.md b/_migrations/migration-phases/assessment/understanding-breaking-changes.md deleted file mode 100644 index 73eaee6e66..0000000000 --- a/_migrations/migration-phases/assessment/understanding-breaking-changes.md +++ /dev/null @@ -1,16 +0,0 @@ - - - -Before performing any upgrade or migration, you should review any documentation of breaking changes. Even if the cluster is migrated there might be changes required for clients to connect to the new cluster - -## Upgrade and breaking changes guides - -For migrations paths between Elasticsearch 6.8 and OpenSearch 2.x users should be familiar with documentation in the links below that apply to their specific case: - -* [Upgrading Amazon Service Domains](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/version-migration.html) ↗ - -* [Changes from Elasticsearch to OpenSearch fork](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/rename.html) ↗ - -* [OpenSearch Breaking Changes](https://opensearch.org/docs/latest/breaking-changes/) ↗ - -The next step is to set up a proper test bed to verify that your applications will work as expected on the target version. diff --git a/_migrations/migration-phases/backfill.md b/_migrations/migration-phases/backfill.md new file mode 100644 index 0000000000..ccdbadd042 --- /dev/null +++ b/_migrations/migration-phases/backfill.md @@ -0,0 +1,174 @@ +--- +layout: default +title: Backfill +nav_order: 90 +parent: Migration phases +--- + +# Backfill + +After the [metadata]({{site.url}}{{site.baseurl}}/migrations/migration-phases/migrating-metadata/) for your cluster has been migrated, you can use capture proxy data replication and snapshots to backfill your data into the next cluster. + +## Capture proxy data replication + +If you're interested in capturing live traffic during your migration, Migration Assistant includes an Application Load Balancer for routing traffic to the capture proxy and the target cluster. Upstream client traffic must be routed through the capture proxy in order to replay the requests later. Before using the capture proxy, remember the following: + +* The layer upstream from the Application Load Balancer is compatible with the certificate on the Application Load Balancer listener, whether it's for clients or a Network Load Balancer. The `albAcmCertArn` in the `cdk.context.json` may need to be provided to ensure that clients trust the Application Load Balancer certificate. +* If a Network Load Balancer is used directly upstream of the Application Load Balancer, it must use a TLS listener. +* Upstream resources and security groups must allow network access to the Migration Assistant Application Load Balancer. + +To set up the capture proxy, go to the AWS Management Console and navigate to **EC2 > Load Balancers > Migration Assistant Application Load Balancer**. Copy the Application Load Balancer URL. With the URL copied, you can use one of the following options. + + +### If you are using **Network Load Balancer → Application Load Balancer → Cluster** + +1. Ensure that ingress is provided directly to the Application Load Balancer for the capture proxy. +2. Create a target group for the Migration Assistant Application Load Balancer on port `9200`, and set the health check to `HTTPS`. +3. Associate this target group with your existing Network Load Balancer on a new listener for testing. +4. Verify that the health check is successful, and perform smoke testing with some clients through the new listener port. +5. Once you are ready to migrate all clients, detach the Migration Assistant Application Load Balancer target group from the testing Network Load Balancer listener and modify the existing Network Load Balancer listener to direct traffic to this target group. +6. Now client requests will be routed through the proxy (once they establish a new connection). Verify the application metrics. + +### If you are using **Network Load Balancer → Cluster** + +If you do not want to modify application logic, add an Application Load Balancer in front of your cluster and follow the **Network Load Balancer → Application Load Balancer → Cluster** steps. Otherwise: + +1. Create a target group for the Application Load Balancer on port `9200` and set the health check to `HTTPS`. +2. Associate this target group with your existing Network Load Balancer on a new listener. +3. Verify that the health check is successful, and perform smoke testing with some clients through the new listener port. +4. Once you are ready to migrate all clients, deploy a change so that clients hit the new listener. + + +### If you are **not using an Network Load Balancer** + +If you're only using backfill as your migration technique, make a client/DNS change to route clients to the Migration Assistant Application Load Balancer on port `9200`. + + +### Kafka connection + +After you have routed the client based on your use case, test adding records against HTTP requests using the following steps: + +1. In the migration console, run the following command: + + ```shell + console kafka describe-topic-records + ``` + + Note the records in the logging topic. + +2. After a short period, execute the same command again and compare the increased number of records against the expected HTTP requests. + + +## Creating a snapshot + +Create a snapshot for your backfill using the following command: + +```bash +console snapshot create +``` + +To check the progress of your snapshot, use the following command: + +```bash +console snapshot status --deep-check +``` + +Depending on the size of the data in the source cluster and the bandwidth allocated for snapshots, the process can take some time. Adjust the maximum rate at which the source cluster's nodes create the snapshot using the `--max-snapshot-rate-mb-per-node` option. Increasing the snapshot rate will consume more node resources, which may affect the cluster's ability to handle normal traffic. + +## Backfilling documents to the source cluster + +From the snapshot you created of your source cluster, you can begin backfilling documents into the target cluster. Once you have started this process, a fleet of workers will spin up to read the snapshot and reindex documents into the target cluster. This fleet of workers can be scaled to increased the speed at which documents are reindexed into the target cluster. + +### Checking the starting state of the clusters + +You can check the indexes and document counts of the source and target clusters by running the `cat-indices` command. This can be used to monitor the difference between the source and target for any migration scenario. Check the indexes of both clusters using the following command: + +```shell +console clusters cat-indices +``` + +You should receive the following response: + +```shell +SOURCE CLUSTER +health status index uuid pri rep docs.count docs.deleted store.size pri.store.size +green open my-index WJPVdHNyQ1KMKol84Cy72Q 1 0 8 0 44.7kb 44.7kb + +TARGET CLUSTER +health status index uuid pri rep docs.count docs.deleted store.size pri.store.size +green open .opendistro_security N3uy88FGT9eAO7FTbLqqqA 1 0 10 0 78.3kb 78.3kb +``` + +### Starting the backfill + +Use the following command to start the backfill and deploy the workers: + +```shell +console backfill start +``` + +You should receive a response similar to the following: + +```shell +BackfillStatus.RUNNING +Running=1 +Pending=0 +Desired=1 +Shards total: 48 +Shards completed: 48 +Shards incomplete: 0 +Shards in progress: 0 +Shards unclaimed: 0 +``` + +The status will be `Running` even if all the shards have been migrated. + +### Scaling up the fleet + +To speed up the transfer, you can scale the number of workers. It may take a few minutes for these additional workers to come online. The following command will update the worker fleet to a size of 10: + +```shell +console backfill scale 5 +``` + +We recommend slowly scaling up the fleet while monitoring the health metrics of the target cluster to avoid over-saturating it. [Amazon OpenSearch Service domains](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/monitoring.html) provide a number of metrics and logs that can provide this insight. + +### Stopping the migration + +Backfill requires manually stopping the fleet. Once all the data has been migrated, you can shut down the fleet and all its workers using the following command: +Backfill requires manually stopping the fleet. Once all the data has been migrated, you can shut down the fleet and all its workers using the following command: +```shell +console backfill stop +``` + +### Amazon CloudWatch metrics and dashboard + +Migration Assistant creates an Amazon CloudWatch dashboard that you can use to visualize the health and performance of the backfill process. It combines the metrics for the backfill workers and, for those migrating to Amazon OpenSearch Service, the target cluster. + +You can find the backfill dashboard in the CloudWatch console based on the AWS Region in which you have deployed Migration Assistant. The metric graphs for your target cluster will be blank until you select the OpenSearch domain you're migrating to from the dropdown menu at the top of the dashboard. + +## Validating the backfill + +After the backfill is complete and the workers have stopped, examine the contents of your cluster using the [Refresh API](https://opensearch.org/docs/latest/api-reference/index-apis/refresh/) and the [Flush API](https://opensearch.org/docs/latest/api-reference/index-apis/flush/). The following example uses the console CLI with the Refresh API to check the backfill status: + +```shell +console clusters cat-indices --refresh +``` + +This will display the number of documents in each of the indexes in the target cluster, as shown in the following example response: + +```shell +SOURCE CLUSTER +health status index uuid pri rep docs.count docs.deleted store.size pri.store.size +green open my-index -DqPQDrATw25hhe5Ss34bQ 1 0 3 0 12.7kb 12.7kb + +TARGET CLUSTER +health status index uuid pri rep docs.count docs.deleted store.size pri.store.size +green open .opensearch-observability 8HOComzdSlSWCwqWIOGRbQ 1 1 0 0 416b 208b +green open .plugins-ml-config 9tld-PCJToSUsMiyDhlyhQ 5 1 1 0 9.5kb 4.7kb +green open my-index bGfGtYoeSU6U6p8leR5NAQ 1 0 3 0 5.5kb 5.5kb +green open .migrations_working_state lopd47ReQ9OEhw4ZuJGZOg 1 1 2 0 18.6kb 6.4kb +green open .kibana_1 +``` + +You can run additional queries against the target cluster to mimic your production workflow and closely examine the results. diff --git a/_migrations/migration-phases/backfill/backfill-execution.md b/_migrations/migration-phases/backfill/backfill-execution.md deleted file mode 100644 index 828317bada..0000000000 --- a/_migrations/migration-phases/backfill/backfill-execution.md +++ /dev/null @@ -1,94 +0,0 @@ - - - -After the [[Metadata Migration]] has been completed; begin the backfill of documents from the snapshot of the source cluster. - -## Document Migration -Once started a fleet of workers will spin up to read the snapshot and reindex documents on the target cluster. This fleet of workers can be scaled to increased the speed that documents are reindexed onto target cluster. - -### Check the starting state of the clusters - -You can see the indices and rough document counts of the source and target cluster by running the cat-indices command. This can be used to monitor the difference between the source and target for any migration scenario. Check the indices of both clusters with the following command: - -```shell -console clusters cat-indices -``` - -
- -Example cat-indices command output - - -```shell -SOURCE CLUSTER -health status index uuid pri rep docs.count docs.deleted store.size pri.store.size -green open my-index WJPVdHNyQ1KMKol84Cy72Q 1 0 8 0 44.7kb 44.7kb - -TARGET CLUSTER -health status index uuid pri rep docs.count docs.deleted store.size pri.store.size -green open .opendistro_security N3uy88FGT9eAO7FTbLqqqA 1 0 10 0 78.3kb 78.3kb -``` -
- -### Start the backfill - -By starting the backfill by running the following command, which creates a fleet with a single worker: - -```shell -console backfill start -``` - -### Monitor the status - -You can use the status check command to see more detail about things like the number of shards completed, in progress, remaining, and the overall status of the operation: - -```shell -console backfill status --deep-check -``` - -
- -Example status output - - -``` -BackfillStatus.RUNNING -Running=1 -Pending=0 -Desired=1 -Shards total: 48 -Shards completed: 48 -Shards incomplete: 0 -Shards in progress: 0 -Shards unclaimed: 0 -``` -
- ->[!Note] -> The status will be "RUNNING" even if all the shards have been migrated. - -### Scale up the fleet - -To speed up the transfer, you can scale the number of workers. It may take a few minutes for these additional workers to come online. The following command will update the worker fleet to a size of ten: - -```shell -console backfill scale 5 -``` - -### Stopping the migration -Backfill requires manually stopping the fleet. Once all the data has been migrated using by checking the status. You can spin down the fleet and all its workers with the command: - -```shell -console backfill stop -``` - - -## Troubleshooting - -### How to scaling the fleet - -It is recommended to scale up the fleet slowly while monitoring the health metrics of the Target Cluster to avoid over-saturating it. Amazon OpenSearch Domains provide a number of metrics and logs that can provide this insight; refer to [the official documentation on the subject](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/monitoring.html) ↗. The AWS Console for Amazon Opensearch Service surfaces details that can be useful for this as well. - -## Related Links - -- [Technical details for RFS](https://github.com/opensearch-project/opensearch-migrations/blob/main/RFS/docs/DESIGN.md) diff --git a/_migrations/migration-phases/backfill/backfill-result-validation.md b/_migrations/migration-phases/backfill/backfill-result-validation.md deleted file mode 100644 index e6cbd97482..0000000000 --- a/_migrations/migration-phases/backfill/backfill-result-validation.md +++ /dev/null @@ -1,37 +0,0 @@ - - - -After the backfill has been completed and the fleet has been stopped - -## Refresh the target cluster - -Before examining the contents of the target cluster, it is recommended to run a `_refresh` and `_flush` on the target cluster. This will help ensure that the report and metrics of the cluster will be accurate portrayed. - -## Validate documents on target cluster -You can check the contents of the Target Cluster after the migration using the Console CLI: - -``` -console clusters cat-indices --refresh -``` -Example cat-indices command output - -```shell -SOURCE CLUSTER -health status index uuid pri rep docs.count docs.deleted store.size pri.store.size -green open my-index -DqPQDrATw25hhe5Ss34bQ 1 0 3 0 12.7kb 12.7kb - -TARGET CLUSTER -health status index uuid pri rep docs.count docs.deleted store.size pri.store.size -green open .opensearch-observability 8HOComzdSlSWCwqWIOGRbQ 1 1 0 0 416b 208b -green open .plugins-ml-config 9tld-PCJToSUsMiyDhlyhQ 5 1 1 0 9.5kb 4.7kb -green open my-index bGfGtYoeSU6U6p8leR5NAQ 1 0 3 0 5.5kb 5.5kb -green open .migrations_working_state lopd47ReQ9OEhw4ZuJGZOg 1 1 2 0 18.6kb 6.4kb -green open .kibana_1 -``` - -This will display the number of documents on each of the indices on the Target Cluster. It is further recommended to run some queries against the Target Cluster that mimic your production workflow and closely examine the results returned. - -## Related Links - -- [Refresh API](https://opensearch.org/docs/latest/api-reference/index-apis/refresh/) ↗ -- [Flush API](https://opensearch.org/docs/latest/api-reference/index-apis/flush/) ↗ diff --git a/_migrations/migration-phases/backfill/capture-proxy-data-replication.md b/_migrations/migration-phases/backfill/capture-proxy-data-replication.md deleted file mode 100644 index 2ed3b1f4c4..0000000000 --- a/_migrations/migration-phases/backfill/capture-proxy-data-replication.md +++ /dev/null @@ -1,45 +0,0 @@ - - - -The Migration Assistant includes an Application Load Balancer (ALB) for routing traffic to the capture proxy and/or target. Upstream client traffic must be routed through the capture proxy in order to replay the requests later. - -## Assumptions - -* The upstream layer from the ALB is compatible with the certificate on the ALB listener (whether it’s clients or a Network Load Balancer, NLB). - * The `albAcmCertArn` in the `cdk.context.json` may need to be provided to ensure that clients trust the ALB certificate. -* If an NLB is used directly upstream of the ALB, it must use a TLS listener. -* Upstream resources and security groups must allow network access to the Migration Assistant ALB. - -## Steps - -1. In the AWS Console, navigate to **EC2 > Load Balancers > Migration Assistant ALB**. -2. Note down the ALB URL. -3. If you are using **NLB → ALB → Cluster**: - 1. Ensure ingress is provided directly to the ALB for the capture proxy. - 2. Create a target group for the Migration Assistant ALB on port 9200, and set the health check to HTTPS. - 3. Associate this target group with your existing NLB on a new listener (for testing). - 4. Verify the health check is successful, and perform smoke testing with some clients through the new listener port. - 5. Once ready to migrate all clients, detach the Migration Assistant ALB target group from the testing NLB listener and modify the existing NLB listener to direct traffic to this target group. - 6. Now, client requests will be routed through the proxy (once they establish a new connection). Verify application metrics. -4. If you are using **NLB → Cluster**: - 1. If you do not wish to modify application logic, add an ALB in front of your cluster and follow the **NLB → ALB → Cluster** steps. Otherwise: - 2. Create a target group for the ALB on port 9200 and set the health check to HTTPS. - 3. Associate this target group with your existing NLB on a new listener. - 4. Verify the health check is successful, and perform smoke testing with some clients through the new listener port. - 5. Once ready to migrate all clients, deploy a change so that clients hit the new listener. -5. If you are **not using an NLB**: - 1. Make a client/DNS change to route clients to the Migration Assistant ALB on port 9200. -6. In the Migration Console, execute the following command: - ```shell - console kafka describe-topic-records - ``` - Note the records in the logging topic. -7. After a short period, execute the same command again and compare the increase in records against the expected HTTP requests. - -### Troubleshooting - -* Investigate the ALB listener security policy, security groups, ALB certificates, and the proxy's connection to Kafka. - -### Related Links - -- [Migration Console ALB Documentation](https://github.com/opensearch-project/opensearch-migrations/blob/main/docs/ClientTrafficSwinging.md) \ No newline at end of file diff --git a/_migrations/migration-phases/backfill/index.md b/_migrations/migration-phases/backfill/index.md deleted file mode 100644 index 8bdb1e2a8d..0000000000 --- a/_migrations/migration-phases/backfill/index.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -layout: default -title: Backfill -nav_order: 90 -has_children: true -parent: Migration phases ---- \ No newline at end of file diff --git a/_migrations/migration-phases/client-traffic-switchover/Switching-Traffic-from-Source-to-Target.md b/_migrations/migration-phases/client-traffic-switchover/Switching-Traffic-from-Source-to-Target.md deleted file mode 100644 index ea7182989a..0000000000 --- a/_migrations/migration-phases/client-traffic-switchover/Switching-Traffic-from-Source-to-Target.md +++ /dev/null @@ -1,37 +0,0 @@ - - -After the source and target clusters are in sync traffic needs to be switched to the target cluster so the source cluster can be taken offline. - -## Assumptions -- All client traffic is being routed through switchover listener in MigrationAssistant ALB -- Client traffic has been verified to be compatible with Target Cluster -- Target cluster is in a good state to accept client traffic (i.e. backfill/replay is complete as needed) -- Target Proxy Service is deployed - -## Switch Traffic to the Source Cluster -1. Within the AWS Console, navigate to ECS > Migration Assistant Cluster -1. Note down the desired count of the Capture Proxy (it should be > 1) -1. Update the ECS Service of the Target Proxy to be at least as large as the Traffic Capture Proxy -1. Wait for tasks to startup, verify all targets healthy within Target Proxy Service "Load balancer health" -1. Within the AWS Console, navigate to EC2 > Load Balancers > Migration Assistant ALB -1. Navigate to ALB Metrics and examine any information which may be useful - 1. Specifically look at Active Connection Count and New Connection Count and note if theres a big discrepancy, this can indicate a reused connections which affect how traffic will switchover. Once an ALB is re-routed, existing connections will still be routed to the capture proxy until the client/source cluster terminates those. -1. Navigate to the Capture Proxy Target Group (ALBSourceProxy--TG) > Monitoring -1. Examine Metrics Requests, Target (2XX, 3XX, 4XX, 5XX), and Target Response Time, Metrics - 1. Verify that this looks as expected and includes all traffic expected to be included in the switchover - 1. Note details that would help identify anomalies during the switchover including expected response time and response code rate. -1. Navigate back to ALB and click on Target Proxy Target Group (ALBTargetProxy--TG) -1. Verify all expected targets are healthy and none are in draining state -1. Navigate back to ALB and to the Listener on port 9200 -1. Click on the Default rule and Edit -1. Modify the weights of the targets to shift desired traffic over to the target proxy - 1. To perform a full switchover, modify the weight to 1 on Target Proxy and 0 on Source Proxy -1. Click Save Changes -1. Navigate to both SourceProxy and TargetProxy TG Monitoring metrics and verify traffic is shifting over as expected - 1. If connections are being reused by clients, perform any actions if needed to terminate those to get the clients to shift over. - 1. Monitor until SourceProxy TG shows 0 requests when it is known all clients have switchover - -## Troubleshooting - -### Fallback -If needed to fallback, revert the Default rule to have the ALB route to the SourceProxy Target Group \ No newline at end of file diff --git a/_migrations/migration-phases/client-traffic-switchover/index.md b/_migrations/migration-phases/client-traffic-switchover/index.md deleted file mode 100644 index 1a381a0ef6..0000000000 --- a/_migrations/migration-phases/client-traffic-switchover/index.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -layout: default -title: Client traffic switchover -nav_order: 110 -has_children: true -parent: Migration phases ---- \ No newline at end of file diff --git a/_migrations/migration-phases/index.md b/_migrations/migration-phases/index.md index 95cd93b495..b637d4a28d 100644 --- a/_migrations/migration-phases/index.md +++ b/_migrations/migration-phases/index.md @@ -3,4 +3,11 @@ layout: default title: Migration phases nav_order: 50 has_children: true ---- \ No newline at end of file +--- + +This page details how to conduct a migration with Migration Assistant. It encompasses a variety of scenarios including: + +- [**Metadata migration**]({{site.url}}{{site.baseurl}}/migrations/migration-phases/migrating-metadata/): Migrating cluster metadata, such as index settings, aliases, and templates. +- [**Backfill migration**]({{site.url}}{{site.baseurl}}/migrations/migration-phases/backfill/): Migrating existing or historical data from a source to a target cluster. +- **Live traffic migration**: Replicating live ongoing traffic from a source to a target cluster. + diff --git a/_migrations/migration-phases/metadata/Snapshot-Creation.md b/_migrations/migration-phases/metadata/Snapshot-Creation.md deleted file mode 100644 index 683b097d5e..0000000000 --- a/_migrations/migration-phases/metadata/Snapshot-Creation.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -layout: default -title: Snapshot creation -nav_order: 90 -parent: Metadata -grand_parent: Migration phases ---- - - -# Snapshot creation - -Creating a snapshot of the source cluster capture all the metadata and documents to migrate onto a new target cluster. - -## Limitations - -Incremental or "delta" snapshots are not yet supported. For more information, refer to the [tracking issue MIGRATIONS-1624](https://opensearch.atlassian.net/browse/MIGRATIONS-1624). A single snapshot must be used for a backfill. - -## Snapshot Creation from the Console - -Create the initial snapshot on the source cluster with the following command: - -```shell -console snapshot create -``` - -To check the progress of the snapshot in real-time, use the following command: - -```shell -console snapshot status --deep-check -``` - -
-Example Output When a Snapshot is Completed - -```shell -console snapshot status --deep-check - -SUCCESS -Snapshot is SUCCESS. -Percent completed: 100.00% -Data GiB done: 29.211/29.211 -Total shards: 40 -Successful shards: 40 -Failed shards: 0 -Start time: 2024-07-22 18:21:42 -Duration: 0h 13m 4s -Anticipated duration remaining: 0h 0m 0s -Throughput: 38.13 MiB/sec -``` -
- -## Troubleshooting - -### Slow Snapshot Speed - -Depending on the size of the data on the source cluster and the bandwidth allocated for snapshots, the process can take some time. Adjust the maximum rate at which the source cluster's nodes create the snapshot using the `--max-snapshot-rate-mb-per-node` option. Increasing the snapshot rate will consume more node resources, which may affect the cluster's ability to handle normal traffic. If not specified, the default rate for the source cluster's version will be used. For more details, refer to the [Elasticsearch 7.10 snapshot documentation](https://www.elastic.co/guide/en/elasticsearch/reference/7.10/put-snapshot-repo-api.html#put-snapshot-repo-api-request-body) ↗. \ No newline at end of file diff --git a/_migrations/migration-phases/metadata/index.md b/_migrations/migration-phases/metadata/index.md deleted file mode 100644 index ac5618ffd4..0000000000 --- a/_migrations/migration-phases/metadata/index.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -layout: default -title: Metadata -nav_order: 80 -has_children: true -parent: Migration phases ---- \ No newline at end of file diff --git a/_migrations/migration-phases/metadata/Metadata-Migration.md b/_migrations/migration-phases/migrating-metadata.md similarity index 61% rename from _migrations/migration-phases/metadata/Metadata-Migration.md rename to _migrations/migration-phases/migrating-metadata.md index 543a249639..2a4079ca3f 100644 --- a/_migrations/migration-phases/metadata/Metadata-Migration.md +++ b/_migrations/migration-phases/migrating-metadata.md @@ -1,17 +1,53 @@ --- layout: default -title: Metadata migration +title: Migrating metadata nav_order: 85 -parent: Metadata -grand_parent: Migration phases +parent: Migration phases --- -# Metadata migration +# Migrating metadata -Metadata migration is a relatively fast process to execute so we recommend attempting this workflow as early as possible to discover any issues which could impact longer running migration steps. +Metadata migration involves creating a snapshot of your cluster and then migrating the metadata from the snapshot using the migration console. -## Prerequisites -A snapshot of the cluster state will need to be taken, [[guide to create a snapshot|Snapshot Creation]]. +This tool gathers information from a source cluster through a snapshot or through HTTP requests against the source cluster. These snapshots are fully compatible with the backfill process for `Reindex-From-Snapshot` (RFS) scenarios. + +After collecting information on the source cluster, comparisons are made against the target cluster. If running a migration, any metadata items that do not already exist will be created on the target cluster. + +## Creating the snapshot + +Creating a snapshot of the source cluster captures all the metadata and documents to be migrated to a new target cluster. + +Create the initial snapshot of the source cluster using the following command: + +```shell +console snapshot create +``` + +To check the progress of the snapshot in real time, use the following command: + +```shell +console snapshot status --deep-check +``` + +You should receive the following response when the snapshot is created: + +```shell +SUCCESS +Snapshot is SUCCESS. +Percent completed: 100.00% +Data GiB done: 29.211/29.211 +Total shards: 40 +Successful shards: 40 +Failed shards: 0 +Start time: 2024-07-22 18:21:42 +Duration: 0h 13m 4s +Anticipated duration remaining: 0h 0m 0s +Throughput: 38.13 MiB/sec +``` + +### Managing slow snapshot speeds + +Depending on the size of the data in the source cluster and the bandwidth allocated for snapshots, the process can take some time. Adjust the maximum rate at which the source cluster's nodes create the snapshot using the `--max-snapshot-rate-mb-per-node` option. Increasing the snapshot rate will consume more node resources, which may affect the cluster's ability to handle normal traffic. ## Command Arguments @@ -47,9 +83,9 @@ INFO:console_link.models.metadata:Migrating metadata with command: /root/metadat . . ``` - -## Metadata verification with evaluate command + +## Using the `evaluate` command By scanning the contents of the source cluster, applying filtering, and applying modifications a list of all items that will be migrated will be created. Any items not seen in this output will not be migrated onto the target cluster if the migrate command was to be run. This is a safety check before making modifications on the target cluster. @@ -57,12 +93,9 @@ By scanning the contents of the source cluster, applying filtering, and applying console metadata evaluate [...] ``` -
- -Example evaluate command output - +You should receive a response similar to the following: -``` +```bash Starting Metadata Evaluation Clusters: Source: @@ -89,9 +122,9 @@ Migration Candidates: Results: 0 issue(s) detected ``` -
-## Metadata migration with migrate command + +## Using the migrate command Running through the same data as the evaluate command all of the migrated items will be applied onto the target cluster. If re-run multiple times items that were previously migrated will not be recreated. If any items do need to be re-migrated, please delete them from the target cluster and then rerun the evaluate then migrate commands to ensure the desired changes are made. @@ -99,12 +132,9 @@ Running through the same data as the evaluate command all of the migrated items console metadata migrate [...] ``` -
- -Example migrate command output - +You should receive a response similar to the following: -``` +```shell Starting Metadata Migration Clusters: @@ -138,9 +168,12 @@ Results: Before moving on to additional migration steps, it is recommended to confirm details of your cluster. Depending on your configuration, this could be checking the sharding strategy or making sure index mappings are correctly defined by ingesting a test document. -## Troubleshoot issues +## Troubleshooting + +Use these instructions to help troubleshoot the following issues. + +### Access detailed logs -### Access Detailed Logs Metadata migration creates a detailed log file that includes low level tracing information for troubleshooting. For each execution of the program a log file is created inside a shared volume on the migration console named `shared-logs-output` the following command will list all log files, one for each run of the command. ```shell @@ -153,23 +186,21 @@ To inspect the file within the console `cat`, `tail` and `grep` commands line to tail /shared-logs-output/migration-console-default/*/metadata/*.log ``` -### Warnings / Errors inline -There might be `WARN` or `ERROR` elements inline the output, they will be accompanied by a short message, such as `WARN - my_index already exists`. Full information will be in the detailed logs associated with this warnings or errors. +### Warnings and errors -### OpenSearch running in compatibility mode -There might be an error about being unable to update an ES 7.10.2 cluster, this can occur when compatibility mode has been enabled on an OpenSearch cluster please disable it to continue, see [Enable compatibility mode](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/rename.html#rename-upgrade) ↗. +When encountering `WARN` or `ERROR` elements in the response, they will be accompanied by a short message, such as `WARN - my_index already exists`. More information can be found in the detailed logs associated with the warning or error. -## How the tool works +### OpenSearch running in compatibility mode -This tool gathers information from a source cluster, through a snapshot or through HTTP requests against the source cluster. These snapshots are fully compatible with the backfill process for Reindex-From-Snapshot (RFS) scenarios, [[learn more|Backfill-Execution]]. +There might be an error about being unable to update an ES 7.10.2 cluster, this can occur when compatibility mode has been enabled on an OpenSearch cluster disable it to continue, see [Enable compatibility mode](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/rename.html#rename-upgrade). -After collecting information on the source cluster comparisons are made on the target cluster. If running a migration, any metadata items do not already exist will be created on the target cluster. ### Breaking change compatibility -Metadata migration needs to modify data from the source to the target versions to recreate items. Sometimes these features are no longer supported and have been removed from the target version. Sometimes these features are not available on the target version, which is especially true when downgrading. While this tool is meant to make this process easier, it is not exhaustive in its support. When encountering a compatibility issue or an important feature gap for your migration, please [search the issues](https://github.com/opensearch-project/opensearch-migrations/issues) and comment + upvote or a [create a new](https://github.com/opensearch-project/opensearch-migrations/issues/new/choose) issue if one cannot be found. +Metadata migration requires modifying data from the source to the target versions to recreate items. Sometimes these features are no longer supported and have been removed from the target version. Sometimes these features are not available in the target version, which is especially true when downgrading. While this tool is meant to make this process easier, it is not exhaustive in its support. When encountering a compatibility issue or an important feature gap for your migration, [search the issues and comment on the existing issue](https://github.com/opensearch-project/opensearch-migrations/issues) or [create a new](https://github.com/opensearch-project/opensearch-migrations/issues/new/choose) issue if one cannot be found. #### Deprecation of Mapping Types + In Elasticsearch 6.8 the mapping types feature was discontinued in Elasticsearch 7.0+ which has created complexity in migrating to newer versions of Elasticsearch and OpenSearch, [learn more](https://www.elastic.co/guide/en/elasticsearch/reference/7.17/removal-of-types.html) ↗. As Metadata migration supports migrating from ES 6.8 on to the latest versions of OpenSearch this scenario is handled by removing the type mapping types and restructuring the template or index properties. Note that, at the time of this writing multiple type mappings are not supported, [tracking task](https://opensearch.atlassian.net/browse/MIGRATIONS-1778) ↗. @@ -203,4 +234,4 @@ As Metadata migration supports migrating from ES 6.8 on to the latest versions o } ``` -*Technical details are available, [view source code](https://github.com/opensearch-project/opensearch-migrations/blob/main/transformation/src/main/java/org/opensearch/migrations/transformation/rules/IndexMappingTypeRemoval.java).* \ No newline at end of file +For additional technical details, [view the mapping type removal source code](https://github.com/opensearch-project/opensearch-migrations/blob/main/transformation/src/main/java/org/opensearch/migrations/transformation/rules/IndexMappingTypeRemoval.java). diff --git a/_migrations/migration-phases/post-migration-cleanup/index.md b/_migrations/migration-phases/post-migration-cleanup/index.md deleted file mode 100644 index 3f76d3ed34..0000000000 --- a/_migrations/migration-phases/post-migration-cleanup/index.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -layout: default -title: Migration infrastructure teardown -nav_order: 120 -has_children: true -parent: Migration phases ---- \ No newline at end of file diff --git a/_migrations/migration-phases/post-migration-cleanup/Migration-Infrastructure-Teardown.md b/_migrations/migration-phases/removing-migration-infrastructure.md similarity index 60% rename from _migrations/migration-phases/post-migration-cleanup/Migration-Infrastructure-Teardown.md rename to _migrations/migration-phases/removing-migration-infrastructure.md index 779dda81c5..75413f25f0 100644 --- a/_migrations/migration-phases/post-migration-cleanup/Migration-Infrastructure-Teardown.md +++ b/_migrations/migration-phases/removing-migration-infrastructure.md @@ -1,16 +1,20 @@ +--- +layout: default +title: Removing migration infrastructure +nav_order: 120 +parent: Migration phases +--- - +# Removing migration infrastructure After a migration is complete all resources should be removed except for the target cluster, and optionally your Cloudwatch Logs, and Replayer logs. -## Remove Migration Assistant Infrastructure To remove all the CDK stack(s) which get created during a deployment you can execute a command similar to below within the CDK directory -``` +```bash cdk destroy "*" --c contextId= ``` Follow the instructions on the command-line to remove the deployed resources from the AWS account. -> [!Note] -> The AWS Console can also be used to verify, remove, and confirm resources for the Migration Assistant are no longer in the account. \ No newline at end of file +The AWS Management Console can also be used to remove Migration Assistant resources and confirm that they are no longer in the account. \ No newline at end of file diff --git a/_migrations/migration-phases/replay-activation-and-validation/In-flight-Validation.md b/_migrations/migration-phases/replay-activation-and-validation/In-flight-Validation.md deleted file mode 100644 index b2003700f7..0000000000 --- a/_migrations/migration-phases/replay-activation-and-validation/In-flight-Validation.md +++ /dev/null @@ -1,152 +0,0 @@ - - - -The Replayer is a long-running process that makes requests to a target cluster to maintain synchronization with the source cluster and enable users to compare the performance between the two clusters. There are two primary ways to assess how the target requests are being handled: through logs and metrics. - -## Result Logs - -HTTP transactions from the source capture and those resent to the target cluster are logged in files located at `/shared-logs-output/traffic-replayer-default/*/tuples/tuples.log`. The `/shared-logs-output` directory is shared across containers, including the migration console. Users can access these files from the migration console using the same path. Previous runs are also available in gzipped form. - -Each log entry is a newline-delimited JSON object, which contains details of the source and target requests/responses along with other transaction details, such as response times. - -> **Note:** These logs contain the contents of all requests, including Authorization headers and the contents of all HTTP messages. Ensure that access to the migration environment is restricted as these logs serve as a source of truth for determining what happened on both the source and target clusters. Response times for the source refer to the time between the proxy sending the end of a request and receiving the response. While response times for the target are recorded in the same manner, keep in mind that the locations of the capture proxy, replayer, and target may differ, and these logs do not account for the client's location. - -
- -Example Log Entry - - -Below is an example log entry for a `/_cat/indices?v` request sent to both the source and target clusters: - -```json -{ - "sourceRequest": { - "Request-URI": "/_cat/indices?v", - "Method": "GET", - "HTTP-Version": "HTTP/1.1", - "Host": "capture-proxy:9200", - "Authorization": "Basic YWRtaW46YWRtaW4=", - "User-Agent": "curl/8.5.0", - "Accept": "*/*", - "body": "" - }, - "sourceResponse": { - "HTTP-Version": {"keepAliveDefault": true}, - "Status-Code": 200, - "Reason-Phrase": "OK", - "response_time_ms": 59, - "content-type": "text/plain; charset=UTF-8", - "content-length": "214", - "body": "aGVhbHRoIHN0YXR1cyBpbmRleCAgICAgICB..." - }, - "targetRequest": { - "Request-URI": "/_cat/indices?v", - "Method": "GET", - "HTTP-Version": "HTTP/1.1", - "Host": "opensearchtarget", - "Authorization": "Basic YWRtaW46bXlTdHJvbmdQYXNzd29yZDEyMyE=", - "User-Agent": "curl/8.5.0", - "Accept": "*/*", - "body": "" - }, - "targetResponses": [{ - "HTTP-Version": {"keepAliveDefault": true}, - "Status-Code": 200, - "Reason-Phrase": "OK", - "response_time_ms": 721, - "content-type": "text/plain; charset=UTF-8", - "content-length": "484", - "body": "aGVhbHRoIHN0YXR1cyBpbmRleCAgICAgICB..." - }], - "connectionId": "0242acfffe13000a-0000000a-00000005-1eb087a9beb83f3e-a32794b4.0", - "numRequests": 1, - "numErrors": 0 -} -``` -
- -### Decoding Log Content - -The contents of HTTP message bodies are Base64 encoded to handle various types of traffic, including compressed data. To view the logs in a more human-readable format, use the console library `tuples show`. Running the script as follows will produce a `readable-tuples.log` in the home directory: - -```shell -console tuples show --in /shared-logs-output/traffic-replayer-default/d3a4b31e1af4/tuples/tuples.log > readable-tuples.log -``` - -
- -Example log entry would look after running the script - - -```json -{ - "sourceRequest": { - "Request-URI": "/_cat/indices?v", - "Method": "GET", - "HTTP-Version": "HTTP/1.1", - "Host": "capture-proxy:9200", - "Authorization": "Basic YWRtaW46YWRtaW4=", - "User-Agent": "curl/8.5.0", - "Accept": "*/*", - "body": "" - }, - "sourceResponse": { - "HTTP-Version": {"keepAliveDefault": true}, - "Status-Code": 200, - "Reason-Phrase": "OK", - "response_time_ms": 59, - "content-type": "text/plain; charset=UTF-8", - "content-length": "214", - "body": "health status index uuid ..." - }, - "targetRequest": { - "Request-URI": "/_cat/indices?v", - "Method": "GET", - "HTTP-Version": "HTTP/1.1", - "Host": "opensearchtarget", - "Authorization": "Basic YWRtaW46bXlTdHJvbmdQYXNzd29yZDEyMyE=", - "User-Agent": "curl/8.5.0", - "Accept": "*/*", - "body": "" - }, - "targetResponses": [{ - "HTTP-Version": {"keepAliveDefault": true}, - "Status-Code": 200, - "Reason-Phrase": "OK", - "response_time_ms": 721, - "content-type": "text/plain; charset=UTF-8", - "content-length": "484", - "body": "health status index uuid ..." - }], - "connectionId": "0242acfffe13000a-0000000a-00000005-1eb087a9beb83f3e-a32794b4.0", - "numRequests": 1, - "numErrors": 0 -} -``` -
- -## Metrics - -The Replayer emits various OpenTelemetry metrics to CloudWatch, and traces are sent through AWS X-Ray. Here are some useful metrics that help evaluate cluster performance: - -### `sourceStatusCode` - -This metric tracks the HTTP status codes for both the source and target clusters, with dimensions for the HTTP verb (e.g., GET, POST) and the status code families (e.g., 200-299). These dimensions help quickly identify discrepancies between the source and target, such as when DELETE 200s become 4xx or GET 4xx errors turn into 5xx errors. - -### `lagBetweenSourceAndTargetRequests` - -This metric shows the delay between requests hitting the source and target clusters. With a speedup factor greater than 1 and a target cluster that can handle requests efficiently, this value should decrease as the replay progresses, indicating a reduction in replay lag. - -### Additional Metrics - -- **Throughput**: `bytesWrittenToTarget` and `bytesReadFromTarget` indicate the throughput to and from the cluster. -- **Retries**: `numRetriedRequests` tracks the number of requests retried due to status-code mismatches between the source and target. -- **Event Counts**: Various `(*)Count` metrics track the number of specific events that have completed. -- **Durations**: `(*)Duration` metrics measure the duration of each step in the process. -- **Exceptions**: `(*)ExceptionCount` shows the number of exceptions encountered during each processing phase. - -## Troubleshooting - -### CloudWatch Considerations - -Metrics pushed to CloudWatch may experience around a 5-minute visibility lag. CloudWatch also retains higher-resolution data for a shorter period than lower-resolution data. For more details, see [CloudWatch Metrics Retention Policies](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch_concepts.html) ↗. diff --git a/_migrations/migration-phases/replay-activation-and-validation/Synchronized-Cluster-Validation.md b/_migrations/migration-phases/replay-activation-and-validation/Synchronized-Cluster-Validation.md deleted file mode 100644 index a5c1759ae7..0000000000 --- a/_migrations/migration-phases/replay-activation-and-validation/Synchronized-Cluster-Validation.md +++ /dev/null @@ -1,161 +0,0 @@ - - - -This guide covers how to use the Replayer to replay captured traffic from a source cluster to a target cluster during the migration process. The Replayer allows users to verify that the target cluster can handle requests in the same way as the source cluster and catch up to real-time traffic for a smooth migration. - -## Replayer Configurations - -[Replayer settings](Configuration-Options) are configured during the deployment of the Migration Assistant. Make sure to set the authentication mode for the Replayer so it can properly communicate with the target cluster. Refer to the **Limitations** section below for details on how different traffic types are handled. - -### Speedup Factor - -The `--speedup-factor` option, passed via `trafficReplayerExtraArgs`, adjusts the wait times between requests. For example: -- A speedup factor of `2` sends requests at twice the original speed (e.g., a request originally sent every minute will now be sent every 30 seconds). -- A speedup factor of `0.5` will space requests further apart (e.g., requests every 2 minutes instead of every minute). - -This setting can be used to stress test the target cluster or to catch up to real-time traffic, ensuring the target cluster is ready for production client switchover. - -## When to Run the Replayer - -After deploying the Migration Assistant, the Replayer is not running by default. It should be started only after all metadata and documents have been migrated to ensure that recent changes to the source cluster are properly reflected in the target cluster. - -For example, if a document was deleted after a snapshot was taken, starting the Replayer before the document migration is complete may cause the deletion request to execute before the document is even added to the target. Running the Replayer after all other migration processes ensures that the target cluster will be consistent with the source cluster. - -## Using the Replayer - -To manage the Replayer, use the `console replay` command: - -- **Start the Replayer**: - ```bash - console replay start - ``` - This starts the Replayer with the options specified at deployment. - -- **Check Replayer Status**: - ```bash - console replay status - ``` - This command shows whether the Replayer is running, pending, or desired. "Running" shows how many container instances are actively running, "Pending" indicates how many are being provisioned, and "Desired" shows the total number of instances that should be running. - -- **Stop the Replayer**: - ```bash - console replay stop - ``` - -
- -Example Interactions - - -Check the status of the Replayer: -```bash -root@ip-10-0-2-66:~# console replay status -(, 'Running=0\nPending=0\nDesired=0') -``` - -Start the Replayer: -```bash -root@ip-10-0-2-66:~# console replay start -Replayer started successfully. -Service migration-dev-traffic-replayer-default set to 1 desired count. Currently 0 running and 0 pending. -``` - -Stop the Replayer: -```bash -root@ip-10-0-2-66:~# console replay stop -Replayer stopped successfully. -Service migration-dev-traffic-replayer-default set to 0 desired count. Currently 0 running and 0 pending. -``` -
- -### Delivery Guarantees - -The Replayer pulls traffic from Kafka and advances its commit cursor after requests have been sent to the target cluster. This provides an "at least once" delivery guarantee—requests will be replayed, but success is not guaranteed. You will need to monitor metrics, tuple outputs, or external validation to ensure the target cluster is performing as expected. - -## Time Scaling - -The Replayer sends requests in the same order they were received on each connection to the source. However, relative timing between different connections is not guaranteed. For example: - -- **Scenario**: Two connections exist—one sends a PUT request every minute, and the other sends a GET request every second. -- **Behavior**: The Replayer will maintain the sequence within each connection, but the relative timing between the connections (PUTs and GETs) is not preserved. - -### Speedup Factor Example - -Assume a source cluster responds to requests (GETs and PUTs) within 100ms: -- With a **speedup factor of 1**, the target will experience the same request rates and idle periods as the source. -- With a **speedup factor of 2**, requests will be sent twice as fast, with GETs sent every 500ms and PUTs every 30 seconds. -- At a **speedup factor of 10**, requests will be sent 10x faster, and as long as the target responds quickly, the Replayer can keep pace. - -If the target cannot respond fast enough, the Replayer will wait for the previous request to complete before sending the next one. This may cause delays and affect global relative ordering. - -## Transformations - -During migrations, some requests may need to be transformed between versions. For example, Elasticsearch supported multiple type mappings in indices, but this is no longer the case in OpenSearch. Clients may need to adjust accordingly by splitting documents into multiple indices or transforming request data. - -The Replayer automatically rewrites host and authentication headers, but for more complex transformations, custom transformation rules can be passed via the `--transformer-config` option (as described in the [Traffic Replayer README](https://github.com/opensearch-project/opensearch-migrations/blob/c3d25958a44ec2e7505892b4ea30e5fbfad4c71b/TrafficCapture/trafficReplayer/README.md#transformations)). - -### Example Transformation - -Suppose a source request includes a "tagToExcise" element that needs to be removed and its children promoted, and the URI path includes "extraThingToRemove" which should also be removed. The following Jolt script handles this transformation: - -```json -[{ "JsonJoltTransformerProvider": -[ - { - "script": { - "operation": "shift", - "spec": { - "payload": { - "inlinedJsonBody": { - "top": { - "tagToExcise": { - "*": "payload.inlinedJsonBody.top.&" - }, - "*": "payload.inlinedJsonBody.top.&" - }, - "*": "payload.inlinedJsonBody.&" - }, - "*": "payload.&" - }, - "*": "&" - } - } - }, - { - "script": { - "operation": "modify-overwrite-beta", - "spec": { - "URI": "=split('/extraThingToRemove',@(1,&))" - } - } - }, - { - "script": { - "operation": "modify-overwrite-beta", - "spec": { - "URI": "=join('',@(1,&))" - } - } - } -] -}] -``` - -The resulting request to the target will look like this: - -```http -PUT /oldStyleIndex/moreStuff HTTP/1.0 -host: testhostname - -{"top":{"properties":{"field1":{"type":"text"},"field2":{"type":"keyword"}}}} -``` - -You can pass Base64-encoded transformation scripts via `--transformer-config-base64` for convenience. - -## Troubleshooting - -### Client changes -See [[Required Client Changes]] for more information on how clients will need to be updated. - -### Request Delivery -The Replayer provides an "at least once" delivery guarantee but does not ensure request success when a replayed request arrives at the target cluster. diff --git a/_migrations/migration-phases/replay-activation-and-validation/index.md b/_migrations/migration-phases/replay-activation-and-validation/index.md deleted file mode 100644 index ab8aa175cb..0000000000 --- a/_migrations/migration-phases/replay-activation-and-validation/index.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -layout: default -title: Replay activation and validation -nav_order: 100 -has_children: true -parent: Migration phases ---- \ No newline at end of file diff --git a/_migrations/migration-phases/setup-verification/Client-Traffic-Switchover-Verification.md b/_migrations/migration-phases/setup-verification/Client-Traffic-Switchover-Verification.md deleted file mode 100644 index 617e16db41..0000000000 --- a/_migrations/migration-phases/setup-verification/Client-Traffic-Switchover-Verification.md +++ /dev/null @@ -1,23 +0,0 @@ - - - -The Migrations ALB is deployed with a listener that shifts traffic between the source and target clusters through proxy services. The ALB should start in **Source Passthrough** mode. - -## Verify traffic switchover has completed -1. In the AWS Console, navigate to **EC2 > Load Balancers**. -2. Select the **MigrationAssistant ALB**. -3. Examine the listener on port 9200 and verify that 100% of traffic is directed to the **Source Proxy**. -4. Navigate to the **Migration ECS Cluster** in the AWS Console. -5. Select the **Target Proxy Service**. -6. Verify that the desired count for the service is running: - * If the desired count is not met, update the service to increase it to at least 1 and wait for the service to start. -7. In the **Health and Metrics** tab under **Load balancer health**, verify that all targets are reporting as healthy: - * This confirms the ALB can connect to the target cluster through the target proxy. -8. (Reset) Update the desired count for the **Target Proxy Service** back to its original value in ECS. - -## Troubleshooting - -### Unexpected traffic patterns -* Verify that the target cluster allows traffic ingress from the **Target Proxy Security Group**. -* Navigate to the **Target Proxy ECS Tasks** to investigate any failing tasks: - * Set the "Filter desired status" to "Any desired status" to view all tasks, then navigate to the logs for any stopped tasks. \ No newline at end of file diff --git a/_migrations/migration-phases/setup-verification/Snapshot-Creation-Verification.md b/_migrations/migration-phases/setup-verification/Snapshot-Creation-Verification.md deleted file mode 100644 index 41f55b7eaf..0000000000 --- a/_migrations/migration-phases/setup-verification/Snapshot-Creation-Verification.md +++ /dev/null @@ -1,100 +0,0 @@ - - - -Verify that a snapshot can be created and used for metadata and backfill scenarios. - -### Install the Elasticsearch S3 Repository Plugin - -The snapshot needs to be stored in a location that the Migration Assistant can access. We use AWS S3 as that location, and the Migration Assistant creates an S3 bucket for this purpose. Therefore, it is necessary to install the Elasticsearch S3 Repository Plugin on your source nodes [as described here](https://www.elastic.co/guide/en/elasticsearch/plugins/7.10/repository-s3.html) ↗. - -Additionally, ensure that the plugin has been configured with AWS credentials that allow it to read and write to AWS S3. If your Elasticsearch cluster is running on EC2 or ECS instances with an execution IAM Role, include the necessary S3 permissions. Alternatively, you can store the credentials in the Elasticsearch Key Store [as described here](https://www.elastic.co/guide/en/elasticsearch/plugins/7.10/repository-s3-client.html) ↗. - -### Verifying S3 Repository Plugin Configuration - -You can verify that the S3 Repository Plugin is configured correctly by creating a test snapshot. - -Create an S3 bucket for the snapshot using the following AWS CLI command: - -```shell -aws s3api create-bucket --bucket --region -``` - -Register a new S3 Snapshot Repository on your source cluster using this curl command: - -```shell -curl -X PUT "http://:9200/_snapshot/test_s3_repository" -H "Content-Type: application/json" -d '{ - "type": "s3", - "settings": { - "bucket": "", - "region": "" - } -}' -``` - -You should receive a response like: `{"acknowledged":true}`. - -Create a test snapshot that captures only the cluster's metadata: - -```shell -curl -X PUT "http://:9200/_snapshot/test_s3_repository/test_snapshot_1" -H "Content-Type: application/json" -d '{ - "indices": "", - "ignore_unavailable": true, - "include_global_state": true -}' -``` - -
-Example Response - -You should receive a response like: `{"accepted":true}`. - -Check the AWS Console to confirm that your bucket contains the snapshot. It will appear similar to this: - -![Screenshot 2024-08-06 at 3 25 25 PM](https://github.com/user-attachments/assets/200818a5-e259-4837-aa2a-44c0bd7b099c) -
- -### Cleaning Up After Verification - -To remove the resources created during verification: - -Delete the snapshot: - -```shell -curl -X DELETE "http://:9200/_snapshot/test_s3_repository/test_snapshot_1?pretty" -``` - -Delete the snapshot repository: - -```shell -curl -X DELETE "http://:9200/_snapshot/test_s3_repository?pretty" -``` - -Delete the S3 bucket and its contents: - -```shell -aws s3 rm s3:// --recursive -aws s3api delete-bucket --bucket --region -``` - -### Troubleshooting - -#### Access Denied Error (403) - -If you encounter an error like `AccessDenied (Service: Amazon S3; Status Code: 403)`, verify the following: - -- The IAM role assigned to your Elasticsearch cluster has the necessary S3 permissions. -- The bucket name and region provided in the snapshot configuration match the actual S3 bucket you created. - -#### Older versions of Elasticsearch - -Older versions of the Elasticsearch S3 Repository Plugin may have trouble reading IAM Role credentials embedded in EC2 and ECS Instances. This is due to the copy of the AWS SDK shipped in them being being too old to read the new standard way of retrieving those credentials - [the Instance Metadata Service v2 (IMDSv2) specification](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html). This can result in Snapshot creation failures, with an error message like: - -``` -{"error":{"root_cause":[{"type":"repository_verification_exception","reason":"[migration_assistant_repo] path [rfs-snapshot-repo] is not accessible on master node"}],"type":"repository_verification_exception","reason":"[migration_assistant_repo] path [rfs-snapshot-repo] is not accessible on master node","caused_by":{"type":"i_o_exception","reason":"Unable to upload object [rfs-snapshot-repo/tests-s8TvZ3CcRoO8bvyXcyV2Yg/master.dat] using a single upload","caused_by":{"type":"amazon_service_exception","reason":"Unauthorized (Service: null; Status Code: 401; Error Code: null; Request ID: null)"}}},"status":500} -``` - -If you encounter this issue, you can resolve it by temporarily enabling IMDSv1 on the Instances in your source cluster for the duration of the snapshot. There is a toggle for it available in [the AWS Console](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-options.html), as well as in [the AWS CLI](https://docs.aws.amazon.com/cli/latest/reference/ec2/modify-instance-metadata-options.html#options). Flipping this toggle will turn on the older access model and enable the Elasticsearch S3 Repository Plugin to work as normal. - -### Related Links - -- [Elasticsearch S3 Repository Plugin Configuration Guide](https://www.elastic.co/guide/en/elasticsearch/plugins/7.10/repository-s3-client.html) ↗. diff --git a/_migrations/migration-phases/setup-verification/System-Reset-Before-Migration.md b/_migrations/migration-phases/setup-verification/System-Reset-Before-Migration.md deleted file mode 100644 index 708dbe5589..0000000000 --- a/_migrations/migration-phases/setup-verification/System-Reset-Before-Migration.md +++ /dev/null @@ -1,22 +0,0 @@ - - - -The following steps outline how to reset resources with Migration Assistant before executing the actual migration. At this point all verifications are expected to have been completed. These steps can be performed after [[Accessing the Migration Console]] - -## Replayer Stoppage -To stop a running Replayer service, the following command can be executed: -``` -console replay stop -``` - -## Kafka Reset -The clear all captured traffic from the Kafka topic, the following command can be executed. **Note**: This command will result in the loss of any captured traffic data up to this point by the capture proxy and thus should be used with caution. -``` -console kafka delete-topic -``` - -## Target Cluster Reset -To clear non-system indices from the target cluster that may have been created from testing, the following command can be executed. **Note**: This command will result in the loss of all data on the target cluster and should be used with caution. -``` -console clusters clear-indices --cluster target -``` diff --git a/_migrations/migration-phases/setup-verification/Traffic-Capture-Verification.md b/_migrations/migration-phases/setup-verification/Traffic-Capture-Verification.md deleted file mode 100644 index 8a99a941d3..0000000000 --- a/_migrations/migration-phases/setup-verification/Traffic-Capture-Verification.md +++ /dev/null @@ -1,36 +0,0 @@ - - - -This guide will describe how once the traffic capture proxy is deployed the captured traffic can be verified. - -## Replication setup and validation -1. Navigate to Migration ECS Cluster in AWS Console -1. Navigate to Capture Proxy Service -1. Verify > 0 desired count and running - * if not, update service to increase to at least 1 and wait for startup -1. Within "Load balancer health" on "Health and Metrics" tab, verify all targets are reporting healthy - * This means the ALB is able to connect to the source cluster through the capture proxy -1. Navigate to the Migration Console Terminal -1. Execute `console kafka describe-topic-records` -1. Wait 30 seconds for another elb health check to be recorded -1. Execute `console kafka describe-topic-records` again, Verify RECORDS increased between runs -1. Execute `console replay start` to start the replayer -1. Run `tail -f /shared-logs-output/traffic-replayer-default/*/tuples/tuples.log | jq '.targetResponses[]."Status-Code"'` to confirm that the Kafka requests were sent to the the target and that it responded as expected... If responses don't appear - * check that the migration-console can access the target cluster by running `./catIndices.sh`, which should show indices on the source and target. - * confirm that messages are still being recorded to Kafka. - * check for errors in the replayer logs ("/migration/STAGE/default/traffic-replayer-default") via CloudWatch -1. (Reset) Update Traffic Capture Proxy service desired count back to original value in ECS - -## Troubleshooting - -### Health checks response with 401/403 status code -If the source cluster is configured to require authentication the capture proxy will not be able to verify beyond receiving 401/403 status code for ALB healthchecks - -### Traffic does not reach the source cluster -Verify the Source Cluster allows traffic ingress from Capture Proxy Security Group. - -Look for failing tasks by navigating to Traffic Capture Proxy ECS Tasks. Change "Filter desired status" to "Any desired status" in order to see all tasks and navigate to logs for stopped tasks. - -### Related Links - -- [Traffic Capture Proxy Failure Modes](https://github.com/opensearch-project/opensearch-migrations/blob/main/TrafficCapture/trafficCaptureProxyServer/README.md#failure-modes) diff --git a/_migrations/migration-phases/setup-verification/index.md b/_migrations/migration-phases/setup-verification/index.md deleted file mode 100644 index 5126b7cab6..0000000000 --- a/_migrations/migration-phases/setup-verification/index.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -layout: default -title: Setup verification -nav_order: 70 -has_children: true -parent: Migration phases ---- \ No newline at end of file diff --git a/_migrations/migration-phases/switching-traffic-from-the-source-cluster.md b/_migrations/migration-phases/switching-traffic-from-the-source-cluster.md new file mode 100644 index 0000000000..c0fe834943 --- /dev/null +++ b/_migrations/migration-phases/switching-traffic-from-the-source-cluster.md @@ -0,0 +1,52 @@ +--- +layout: default +title: Switching traffic from the source cluster +nav_order: 110 +parent: Migration phases +--- + +# Switching traffic from the source cluster + +After the source and target clusters are synchronized, traffic needs to be switched to the target cluster so that the source cluster can be taken offline. + +## Assumptions + +This page assumes that the following has occurred before making the switch: + +- All client traffic is being routed through a switchover listener in the [MigrationAssistant Application Load Balancer]({{site.url}}{{site.baseurl}}/migrations/migration-phases/backfill/). +- Client traffic has been verified as compatible with the target cluster. +- The target cluster is in a good state to accept client traffic. +- The target proxy service is deployed. + +## Switching traffic + +Use the following steps to switch traffic from the source cluster to the target cluster: + +1. In the AWS Management Console, navigate to **ECS** > **Migration Assistant Cluster**. Note the desired count of the capture proxy, which should be greater than 1. + +2. Update the **ECS Service** of the target proxy to be at least as large as the traffic capture proxy. Wait for tasks to start up, and verify that all targets are healthy in the target proxy service's **Load balancer health** section. + +3. Navigate to **EC2** > **Load Balancers** > **Migration Assistant ALB**. + +4. Navigate to **ALB Metrics** and examine any useful information, specifically looking at **Active Connection Count** and **New Connection Count**. Note any large discrepancies, which can indicate reused connections affecting traffic switchover. + +5. Navigate to **Capture Proxy Target Group** (`ALBSourceProxy--TG`) > **Monitoring**. + +6. Examine the **Metrics Requests**, **Target (2XX, 3XX, 4XX, 5XX)**, and **Target Response Time** metrics. Verify that this appears as expected and includes all traffic expected to be included in the switchover. Note details that could help identify anomalies during the switchover, including the expected response time and response code rate. + +7. Navigate back to **ALB Metrics** and choose **Target Proxy Target Group** (`ALBTargetProxy--TG`). Verify that all expected targets are healthy and that none are in a draining state. + +8. Navigate back to **ALB Metrics** and to the **Listener** on port `9200`. + +9. Choose the **Default rule** and **Edit**. + +10. Modify the weights of the targets to switch the desired traffic to the target proxy. To perform a full switchover, modify the **Target Proxy** weight to `1` and the **Source Proxy** weight to `0`. + +11. Choose **Save Changes**. + +12. Navigate to both **SourceProxy** and **TargetProxy TG Monitoring** metrics and verify that traffic is switching over as expected. If connections are being reused by clients, perform any necessary actions to terminate them. Monitor these metrics until **SourceProxy TG** shows 0 requests when all clients have switched over. + + +## Fallback + +If you need to fall back to the source cluster at any point during the switchover, revert the **Default rule** so that the Application Load Balancer routes to the **SourceProxy Target Group**. \ No newline at end of file diff --git a/_migrations/migration-phases/using-traffic-replayer.md b/_migrations/migration-phases/using-traffic-replayer.md new file mode 100644 index 0000000000..1c812be211 --- /dev/null +++ b/_migrations/migration-phases/using-traffic-replayer.md @@ -0,0 +1,307 @@ +--- +layout: default +title: Using Traffic Replayer +nav_order: 100 +parent: Migration phases +--- + +# Using Traffic Replayer + +This guide covers how to use Traffic Replayer to replay captured traffic from a source cluster to a target cluster during the migration process. Traffic Replayer allows you to verify that the target cluster can handle requests in the same way as the source cluster and catch up to real-time traffic for a smooth migration. + +## When to run Traffic Replayer + +After deploying Migration Assistant, Traffic Replayer does not run by default. It should be started only after all metadata and documents have been migrated to ensure that recent changes to the source cluster are properly reflected in the target cluster. + +For example, if a document was deleted after a snapshot was taken, starting Traffic Replayer before the document migration is complete may cause the deletion request to execute before the document is added to the target. Running Traffic Replayer after all other migration processes ensures that the target cluster will be consistent with the source cluster. + +## Configuration options + +[Traffic Replayer settings]({{site.url}}{{site.baseurl}}/migrations/deploying-migration-assisstant/configuation-options/) are configured during the deployment of Migration Assistant. Make sure to set the authentication mode for Traffic Replayer so that it can properly communicate with the target cluster. For more information about different types of traffic that are handled by Traffic Replayer, see [limitations](#limitations). + +## Using Traffic Replayer + +To manage Traffic Replayer, use the `console replay` command. The following examples show the available commands. + +### Start Traffic Replayer + +The following command starts Traffic Replayer with the options specified at deployment: + +```bash +console replay start +``` + +When starting Traffic Replayer, you should receive an output similar to the following: + +```bash +root@ip-10-0-2-66:~# console replay start +Replayer started successfully. +Service migration-dev-traffic-replayer-default set to 1 desired count. Currently 0 running and 0 pending. +``` + +## Check the status of Traffic Replayer + +Use the following command to show the status of Traffic Replayer: + +```bash +console replay status +``` + +Replay will return one of the following statuses: + +- `Running` shows how many container instances are actively running. +- `Pending` indicates how many instances are being provisione.d +- `Desired` shows the total number of instances that should be running. + +You should receive an output similar to the following: + +```bash +root@ip-10-0-2-66:~# console replay status +(, 'Running=0\nPending=0\nDesired=0') +``` + +## Stop Traffic Replayer + +The following command stops Traffic Replayer: + +```bash +console replay stop +``` + +You should receive an output similar to the following: + +```bash +root@ip-10-0-2-66:~# console replay stop +Replayer stopped successfully. +Service migration-dev-traffic-replayer-default set to 0 desired count. Currently 0 running and 0 pending. +``` + + + +### Delivery guarantees + +Traffic Replayer retrieves traffic from Kafka and updates its commit cursor after sending requests to the target cluster. This provides an "at least once" delivery guarantee; however, success isn't always guaranteed. Therefore, you should monitor metrics and tuple outputs or perform external validation to ensure that the target cluster is functioning as expected. + +## Time scaling + +Traffic Replayer sends requests in the same order that they were received from each connection to the source. However, relative timing between different connections is not guaranteed. For example: + +- **Scenario**: Two connections exist:one sends a PUT request every minute, and the other sends a GET request every second. +- **Behavior**: Traffic Replayer will maintain the sequence within each connection, but the relative timing between the connections (PUTs and GETs) is not preserved. + +Assume that a source cluster responds to requests (GETs and PUTs) within 100 ms: + +- With a **speedup factor of 1**, the target will experience the same request rates and idle periods as the source. +- With a **speedup factor of 2**, requests will be sent twice as fast, with GETs sent every 500 ms and PUTs every 30 seconds. +- With a **speedup factor of 10**, requests will be sent 10x faster, and as long as the target responds quickly, Traffic Replayer can maintain the pace. + +If the target cannot respond fast enough, Traffic Replayer will wait for the previous request to complete before sending the next one. This may cause delays and affect global relative ordering. + +## Transformations + +During migrations, some requests may need to be transformed between versions. For example, Elasticsearch previously supported multiple type mappings in indexes, but this is no longer the case in OpenSearch. Clients may need to be adjusted accordingly by splitting documents into multiple indexes or transforming request data. + +Traffic Replayer automatically rewrites host and authentication headers, but for more complex transformations, custom transformation rules can be specified using the `--transformer-config` option. For more information, see the [Traffic Replayer README](https://github.com/opensearch-project/opensearch-migrations/blob/c3d25958a44ec2e7505892b4ea30e5fbfad4c71b/TrafficCapture/trafficReplayer/README.md#transformations). + +### Example transformation + +Suppose that a source request contains a `tagToExcise` element that needs to be removed and its children promoted and that the URI path includes `extraThingToRemove`, which should also be removed. The following Jolt script handles this transformation: + +```json +[{ "JsonJoltTransformerProvider": +[ + { + "script": { + "operation": "shift", + "spec": { + "payload": { + "inlinedJsonBody": { + "top": { + "tagToExcise": { + "*": "payload.inlinedJsonBody.top.&" + }, + "*": "payload.inlinedJsonBody.top.&" + }, + "*": "payload.inlinedJsonBody.&" + }, + "*": "payload.&" + }, + "*": "&" + } + } + }, + { + "script": { + "operation": "modify-overwrite-beta", + "spec": { + "URI": "=split('/extraThingToRemove',@(1,&))" + } + } + }, + { + "script": { + "operation": "modify-overwrite-beta", + "spec": { + "URI": "=join('',@(1,&))" + } + } + } +] +}] +``` + +The resulting request sent to the target will appear similar to the following: + +```http +PUT /oldStyleIndex/moreStuff HTTP/1.0 +host: testhostname + +{"top":{"properties":{"field1":{"type":"text"},"field2":{"type":"keyword"}}}} +``` + +You can pass Base64-encoded transformation scripts using `--transformer-config-base64`. + +## Result logs + +HTTP transactions from the source capture and those resent to the target cluster are logged in files located at `/shared-logs-output/traffic-replayer-default/*/tuples/tuples.log`. The `/shared-logs-output` directory is shared across containers, including the migration console. You can access these files from the migration console using the same path. Previous runs are also available in a `gzipped` format. + +Each log entry is a newline-delimited JSON object, containing information about the source and target requests/responses along with other transaction details, such as response times. + +These logs contain the contents of all requests, including authorization headers and the contents of all HTTP messages. Ensure that access to the migration environment is restricted, as these logs serve as a source of truth for determining what happened in both the source and target clusters. Response times for the source refer to the amount of time between the proxy sending the end of a request and receiving the response. While response times for the target are recorded in the same manner, keep in mind that the locations of the capture proxy, Traffic Replayer, and target may differ and that these logs do not account for the client's location. +{: .note} + + +### Example log entry + +The following example log entry shows a `/_cat/indices?v` request sent to both the source and target clusters: + +```json +{ + "sourceRequest": { + "Request-URI": "/_cat/indices?v", + "Method": "GET", + "HTTP-Version": "HTTP/1.1", + "Host": "capture-proxy:9200", + "Authorization": "Basic YWRtaW46YWRtaW4=", + "User-Agent": "curl/8.5.0", + "Accept": "*/*", + "body": "" + }, + "sourceResponse": { + "HTTP-Version": {"keepAliveDefault": true}, + "Status-Code": 200, + "Reason-Phrase": "OK", + "response_time_ms": 59, + "content-type": "text/plain; charset=UTF-8", + "content-length": "214", + "body": "aGVhbHRoIHN0YXR1cyBpbmRleCAgICAgICB..." + }, + "targetRequest": { + "Request-URI": "/_cat/indices?v", + "Method": "GET", + "HTTP-Version": "HTTP/1.1", + "Host": "opensearchtarget", + "Authorization": "Basic YWRtaW46bXlTdHJvbmdQYXNzd29yZDEyMyE=", + "User-Agent": "curl/8.5.0", + "Accept": "*/*", + "body": "" + }, + "targetResponses": [{ + "HTTP-Version": {"keepAliveDefault": true}, + "Status-Code": 200, + "Reason-Phrase": "OK", + "response_time_ms": 721, + "content-type": "text/plain; charset=UTF-8", + "content-length": "484", + "body": "aGVhbHRoIHN0YXR1cyBpbmRleCAgICAgICB..." + }], + "connectionId": "0242acfffe13000a-0000000a-00000005-1eb087a9beb83f3e-a32794b4.0", + "numRequests": 1, + "numErrors": 0 +} +``` + + +### Decoding log content + +The contents of HTTP message bodies are Base64 encoded in order to handle various types of traffic, including compressed data. To view the logs in a more human-readable format, use the console library `tuples show`. Running the script as follows will produce a `readable-tuples.log` in the home directory: + +```shell +console tuples show --in /shared-logs-output/traffic-replayer-default/d3a4b31e1af4/tuples/tuples.log > readable-tuples.log +``` + +The `readable-tuples.log` should appear similar to the following: + +```json +{ + "sourceRequest": { + "Request-URI": "/_cat/indices?v", + "Method": "GET", + "HTTP-Version": "HTTP/1.1", + "Host": "capture-proxy:9200", + "Authorization": "Basic YWRtaW46YWRtaW4=", + "User-Agent": "curl/8.5.0", + "Accept": "*/*", + "body": "" + }, + "sourceResponse": { + "HTTP-Version": {"keepAliveDefault": true}, + "Status-Code": 200, + "Reason-Phrase": "OK", + "response_time_ms": 59, + "content-type": "text/plain; charset=UTF-8", + "content-length": "214", + "body": "health status index uuid ..." + }, + "targetRequest": { + "Request-URI": "/_cat/indices?v", + "Method": "GET", + "HTTP-Version": "HTTP/1.1", + "Host": "opensearchtarget", + "Authorization": "Basic YWRtaW46bXlTdHJvbmdQYXNzd29yZDEyMyE=", + "User-Agent": "curl/8.5.0", + "Accept": "*/*", + "body": "" + }, + "targetResponses": [{ + "HTTP-Version": {"keepAliveDefault": true}, + "Status-Code": 200, + "Reason-Phrase": "OK", + "response_time_ms": 721, + "content-type": "text/plain; charset=UTF-8", + "content-length": "484", + "body": "health status index uuid ..." + }], + "connectionId": "0242acfffe13000a-0000000a-00000005-1eb087a9beb83f3e-a32794b4.0", + "numRequests": 1, + "numErrors": 0 +} +``` + + +## Metrics + +Traffic Replayer emits various OpenTelemetry metrics to Amazon CloudWatch, and traces are sent through AWS X-Ray. The following are some useful metrics that can help evaluate cluster performance. + +### `sourceStatusCode` + +This metric tracks the HTTP status codes for both the source and target clusters, with dimensions for the HTTP verb, such as `GET` or `POST`, and the status code families (200--299). These dimensions can help quickly identify discrepancies between the source and target, such as when `DELETE 200s` becomes `4xx` or `GET 4xx` errors turn into `5xx` errors. + +### `lagBetweenSourceAndTargetRequests` + +This metric shows the delay between requests hitting the source and target clusters. With a speedup factor greater than 1 and a target cluster that can handle requests efficiently, this value should decrease as the replay progresses, indicating a reduction in replay lag. + +### Additional metrics + +The following metrics are also reported: + +- **Throughput**: `bytesWrittenToTarget` and `bytesReadFromTarget` indicate the throughput to and from the cluster. +- **Retries**: `numRetriedRequests` tracks the number of requests retried due to status code mismatches between the source and target. +- **Event counts**: Various `(*)Count` metrics track the number of completed events. +- **Durations**: `(*)Duration` metrics measure the duration of each step in the process. +- **Exceptions**: `(*)ExceptionCount` shows the number of exceptions encountered during each processing phase. + + +## CloudWatch considerations + +Metrics pushed to CloudWatch may experience a visibility lag of around 5 minutes. CloudWatch also retains higher-resolution data for a shorter period than lower-resolution data. For more information, see [Amazon CloudWatch concepts](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch_concepts.html). \ No newline at end of file diff --git a/_migrations/migration-phases/verifying-migration-tools.md b/_migrations/migration-phases/verifying-migration-tools.md new file mode 100644 index 0000000000..498ed50feb --- /dev/null +++ b/_migrations/migration-phases/verifying-migration-tools.md @@ -0,0 +1,196 @@ +--- +layout: default +title: Verifying migration tools +nav_order: 70 +parent: Migration phases +--- + +# Verifying migration tools + +Before using the Migration Assistant, take the following steps to verify that your cluster is ready for migration. + +## Snapshot creation verification + +Verify that a snapshot can be created of your source cluster and used for metadata and backfill scenarios. + +### Installing the Elasticsearch S3 Repository plugin + +The snapshot needs to be stored in a location that Migration Assistant can access. This guide uses Amazon Simple Storage Service (Amazon S3). By default, Migration Assistant creates an S3 bucket for storage. Therefore, it is necessary to install the [Elasticsearch S3 repository plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/7.10/repository-s3.html) on your source nodes (https://www.elastic.co/guide/en/elasticsearch/plugins/7.10/repository-s3.html). + +Additionally, make sure that the plugin has been configured with AWS credentials that allow it to read and write to Amazon S3. If your Elasticsearch cluster is running on Amazon Elastic Compute Cloud (Amazon EC2) or Amazon Elastic Container Service (Amazon ECS) instances with an AWS Identity and Access Management (IAM) execution role, include the necessary S3 permissions. Alternatively, you can store the credentials in the [Elasticsearch keystore](https://www.elastic.co/guide/en/elasticsearch/plugins/7.10/repository-s3-client.html). + +### Verifying the S3 repository plugin configuration + +You can verify that the S3 repository plugin is configured correctly by creating a test snapshot. + +Create an S3 bucket for the snapshot using the following AWS Command Line Interface (AWS CLI) command: + +```shell +aws s3api create-bucket --bucket --region +``` + +Register a new S3 snapshot repository on your source cluster using the following cURL command: + +```shell +curl -X PUT "http://:9200/_snapshot/test_s3_repository" -H "Content-Type: application/json" -d '{ + "type": "s3", + "settings": { + "bucket": "", + "region": "" + } +}' +``` + +Next, create a test snapshot that captures only the cluster's metadata: + +```shell +curl -X PUT "http://:9200/_snapshot/test_s3_repository/test_snapshot_1" -H "Content-Type: application/json" -d '{ + "indices": "", + "ignore_unavailable": true, + "include_global_state": true +}' +``` + +Check the AWS Management Console to confirm that your bucket contains the snapshot. + +### Removing test snapshots after verification + +To remove the resources created during verification, you can use the following deletion commands: + +**Test snapshot** + +```shell +curl -X DELETE "http://:9200/_snapshot/test_s3_repository/test_snapshot_1?pretty" +``` + +**Test snapshot repository** + +```shell +curl -X DELETE "http://:9200/_snapshot/test_s3_repository?pretty" +``` + +**S3 bucket** + +```shell +aws s3 rm s3:// --recursive +aws s3api delete-bucket --bucket --region +``` + +### Troubleshooting + +Use this guidance to troubleshoot any of the following snapshot verification issues. + +#### Access denied error (403) + +If you encounter an error like `AccessDenied (Service: Amazon S3; Status Code: 403)`, verify the following: + +- The IAM role assigned to your Elasticsearch cluster has the necessary S3 permissions. +- The bucket name and AWS Region provided in the snapshot configuration match the actual S3 bucket you created. + +#### Older versions of Elasticsearch + +Older versions of the Elasticsearch S3 repository plugin may have trouble reading IAM role credentials embedded in Amazon EC2 and Amazon ECS instances. This is because the copy of the AWS SDK shipped with them is too old to read the new standard way of retrieving those credentials, as shown in [the Instance Metadata Service v2 (IMDSv2) specification](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html). This can result in snapshot creation failures, with an error message similar to the following: + +```json +{"error":{"root_cause":[{"type":"repository_verification_exception","reason":"[migration_assistant_repo] path [rfs-snapshot-repo] is not accessible on master node"}],"type":"repository_verification_exception","reason":"[migration_assistant_repo] path [rfs-snapshot-repo] is not accessible on master node","caused_by":{"type":"i_o_exception","reason":"Unable to upload object [rfs-snapshot-repo/tests-s8TvZ3CcRoO8bvyXcyV2Yg/master.dat] using a single upload","caused_by":{"type":"amazon_service_exception","reason":"Unauthorized (Service: null; Status Code: 401; Error Code: null; Request ID: null)"}}},"status":500} +``` + +If you encounter this issue, you can resolve it by temporarily enabling IMDSv1 on the instances in your source cluster for the duration of the snapshot. There is a toggle for this available in the AWS Management Console as well as in the AWS CLI. Switching this toggle will turn on the older access model and enable the Elasticsearch S3 repository plugin to work as normal. For more information about IMDSv1, see [Modify instance metadata options for existing instances](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-IMDS-existing-instances.html). + +## Switching over client traffic + +The Migration Assistant Application Load Balancer is deployed with a listener that shifts traffic between the source and target clusters through proxy services. The Application Load Balancer should start in **Source Passthrough** mode. + +### Verifying that the traffic switchover is complete + +Use the following steps to verify that the traffic switchover is complete: + +1. In the AWS Management Console, navigate to **EC2 > Load Balancers**. +2. Select the **MigrationAssistant ALB**. +3. Examine the listener on port `9200` and verify that 100% of the traffic is directed to the **Source Proxy**. +4. Navigate to the **Migration ECS Cluster** in the AWS Management Console. +5. Select the **Target Proxy Service**. +6. Verify that the desired count for the service is running: + * If the desired count is not met, update the service to increase it to at least 1 and wait for the service to start. +7. On the **Health and Metrics** tab under **Load balancer health**, verify that all targets are reporting as healthy: + * This confirms that the Application Load Balancer can connect to the target cluster through the target proxy. +8. (Reset) Update the desired count for the **Target Proxy Service** back to its original value in Amazon ECS. + +### Fixing unidentified traffic patterns + +When switching over traffic to the target cluster, you might encounter unidentified traffic patterns. To help identify the cause of these patterns, use the following steps: +* Verify that the target cluster allows traffic ingress from the **Target Proxy Security Group**. +* Navigate to **Target Proxy ECS Tasks** to investigate any failing tasks. +Set the **Filter desired status** to **Any desired status** to view all tasks, then navigate to the logs for any stopped tasks. + + +## Verifying replication + +Use the following steps to verify that replication is working once the traffic capture proxy is deployed: + + +1. Navigate to the **Migration ECS Cluster** in the AWS Management Console. +2. Navigate to **Capture Proxy Service**. +3. Verify that the capture proxy is running with the desired proxy count. If it is not, update the service to increase it to at least 1 and wait for startup. +4. Under **Health and Metrics** > **Load balancer health**, verify that all targets are healthy. This means that the Application Load Balancer is able to connect to the source cluster through the capture proxy. +5. Navigate to the **Migration Console Terminal**. +6. Run `console kafka describe-topic-records`. Wait 30 seconds for another Application Load Balancer health check. +7. Run `console kafka describe-topic-records` again and verify that the number of RECORDS increased between runs. +8. Run `console replay start` to start Traffic Replayer. +9. Run `tail -f /shared-logs-output/traffic-replayer-default/*/tuples/tuples.log | jq '.targetResponses[]."Status-Code"'` to confirm that the Kafka requests were sent to the target and that it responded as expected. If the responses don't appear: + * Check that the migration console can access the target cluster by running `./catIndices.sh`, which should show the indexes in the source and target. + * Confirm that messages are still being recorded to Kafka. + * Check for errors in the Traffic Replayer logs (`/migration/STAGE/default/traffic-replayer-default`) using CloudWatch. +10. (Reset) Update the desired count for the **Capture Proxy Service** back to its original value in Amazon ECS. + +### Troubleshooting + +Use this guidance to troubleshoot any of the following replication verification issues. + +### Health check responses with 401/403 status code + +If the source cluster is configured to require authentication, the capture proxy will not be able to verify replication beyond receiving a 401/403 status code for Application Load Balancer health checks. For more information, see [Failure Modes](https://github.com/opensearch-project/opensearch-migrations/blob/main/TrafficCapture/trafficCaptureProxyServer/README.md#failure-modes). + +### Traffic does not reach the source cluster + +Verify that the source cluster allows traffic ingress from the Capture Proxy Security Group. + +Look for failing tasks by navigating to **Traffic Capture Proxy ECS**. Change **Filter desired status** to **Any desired status** in order to see all tasks and navigate to the logs for stopped tasks. + + +## Resetting before migration + +After all verifications are complete, reset all resources before using Migration Assistant for an actual migration. + +The following steps outline how to reset resources with Migration Assistant before executing the actual migration. At this point all verifications are expected to have been completed. These steps can be performed after [Accessing the Migration Console]({{site.url}}{{site.baseurl}}/migrations/migration-console/accessing-the-migration-console/). + +### Traffic Replayer + +To stop running Traffic Replayer, use the following command: + +```bash +console replay stop +``` + +### Kafka + +To clear all captured traffic from the Kafka topic, you can run the following command. + +This command will result in the loss of any traffic data captured by the capture proxy up to this point and thus should be used with caution. +{: .warning} + +```bash +console kafka delete-topic +``` + +### Target cluster + +To clear non-system indexes from the target cluster that may have been created as a result of testing, you can run the following command: + +This command will result in the loss of all data in the target cluster and should be used with caution. +{: .warning} + +```bash +console clusters clear-indices --cluster target +``` + From 8017292dcf4ba073bf754d577b8fe31067226fcc Mon Sep 17 00:00:00 2001 From: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> Date: Tue, 3 Dec 2024 15:54:53 -0600 Subject: [PATCH 29/69] Update getting-started-data-migration.md (#8870) Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> --- _migrations/getting-started-data-migration.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_migrations/getting-started-data-migration.md b/_migrations/getting-started-data-migration.md index b788522d5a..035ddae323 100644 --- a/_migrations/getting-started-data-migration.md +++ b/_migrations/getting-started-data-migration.md @@ -1,10 +1,10 @@ --- layout: default -title: Quickstart: Data migration +title: Getting started with data migration nav_order: 10 --- -# Getting started: Data migration +# Getting started with data migration This quickstart outlines how to deploy Migration Assistant for OpenSearch and execute an existing data migration using `Reindex-from-Snapshot` (RFS). It uses AWS for illustrative purposes. However, the steps can be modified for use with other cloud providers. From c36b62cd41ef15d4e60974d51b7c8e9722957eaf Mon Sep 17 00:00:00 2001 From: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Date: Tue, 3 Dec 2024 17:19:18 -0500 Subject: [PATCH 30/69] Add links to tokenizers from index page (#8868) Signed-off-by: Fanit Kolchina --- _analyzers/token-filters/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_analyzers/token-filters/index.md b/_analyzers/token-filters/index.md index 96efdeae15..ed2c3ab349 100644 --- a/_analyzers/token-filters/index.md +++ b/_analyzers/token-filters/index.md @@ -17,7 +17,7 @@ The following table lists all token filters that OpenSearch supports. Token filter | Underlying Lucene token filter| Description [`apostrophe`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/apostrophe/) | [ApostropheFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/tr/ApostropheFilter.html) | In each token containing an apostrophe, the `apostrophe` token filter removes the apostrophe itself and all characters following it. [`asciifolding`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/asciifolding/) | [ASCIIFoldingFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/ASCIIFoldingFilter.html) | Converts alphabetic, numeric, and symbolic characters. -`cjk_bigram` | [CJKBigramFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/cjk/CJKBigramFilter.html) | Forms bigrams of Chinese, Japanese, and Korean (CJK) tokens. +[`cjk_bigram`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/cjk-bigram/) | [CJKBigramFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/cjk/CJKBigramFilter.html) | Forms bigrams of Chinese, Japanese, and Korean (CJK) tokens. [`cjk_width`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/cjk-width/) | [CJKWidthFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/cjk/CJKWidthFilter.html) | Normalizes Chinese, Japanese, and Korean (CJK) tokens according to the following rules:
- Folds full-width ASCII character variants into their equivalent basic Latin characters.
- Folds half-width katakana character variants into their equivalent kana characters. [`classic`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/classic) | [ClassicFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/classic/ClassicFilter.html) | Performs optional post-processing on the tokens generated by the classic tokenizer. Removes possessives (`'s`) and removes `.` from acronyms. [`common_grams`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/common_gram/) | [CommonGramsFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/commongrams/CommonGramsFilter.html) | Generates bigrams for a list of frequently occurring terms. The output contains both single terms and bigrams. @@ -29,7 +29,7 @@ Token filter | Underlying Lucene token filter| Description [`edge_ngram`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/edge-ngram/) | [EdgeNGramTokenFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/ngram/EdgeNGramTokenFilter.html) | Tokenizes the given token into edge n-grams (n-grams that start at the beginning of the token) of lengths between `min_gram` and `max_gram`. Optionally, keeps the original token. [`elision`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/elision/) | [ElisionFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/util/ElisionFilter.html) | Removes the specified [elisions](https://en.wikipedia.org/wiki/Elision) from the beginning of tokens. For example, changes `l'avion` (the plane) to `avion` (plane). [`fingerprint`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/fingerprint/) | [FingerprintFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/FingerprintFilter.html) | Sorts and deduplicates the token list and concatenates tokens into a single token. -`flatten_graph` | [FlattenGraphFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/core/FlattenGraphFilter.html) | Flattens a token graph produced by a graph token filter, such as `synonym_graph` or `word_delimiter_graph`, making the graph suitable for indexing. +[`flatten_graph`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/flatten-graph/) | [FlattenGraphFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/core/FlattenGraphFilter.html) | Flattens a token graph produced by a graph token filter, such as `synonym_graph` or `word_delimiter_graph`, making the graph suitable for indexing. [`hunspell`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/hunspell/) | [HunspellStemFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/hunspell/HunspellStemFilter.html) | Uses [Hunspell](https://en.wikipedia.org/wiki/Hunspell) rules to stem tokens. Because Hunspell allows a word to have multiple stems, this filter can emit multiple tokens for each consumed token. Requires the configuration of one or more language-specific Hunspell dictionaries. [`hyphenation_decompounder`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/hyphenation-decompounder/) | [HyphenationCompoundWordTokenFilter](https://lucene.apache.org/core/9_8_0/analysis/common/org/apache/lucene/analysis/compound/HyphenationCompoundWordTokenFilter.html) | Uses XML-based hyphenation patterns to find potential subwords in compound words and checks the subwords against the specified word list. The token output contains only the subwords found in the word list. [`keep_types`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/keep-types/) | [TypeTokenFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/core/TypeTokenFilter.html) | Keeps or removes tokens of a specific type. From 1cd69cd6bd5c1483cbea87bd50161ad7a4dd4eb9 Mon Sep 17 00:00:00 2001 From: Andrew Ross Date: Wed, 4 Dec 2024 06:03:05 -0800 Subject: [PATCH 31/69] Document the `X-Opaque-Id` header (#8820) * Document the `X-Opaque-Id` header A [bug][1] was recently identified related to this header that was partly exascerbated by a misuse of the field. We have some documentation related to the [Tasks][2] API for this field, but no general guidance on how to use it. [1]: https://github.com/opensearch-project/OpenSearch/issues/16702 [2]: https://opensearch.org/docs/latest/api-reference/tasks/#attaching-headers-to-tasks Signed-off-by: Andrew Ross * Update _api-reference/common-parameters.md Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Signed-off-by: Andrew Ross * Update _api-reference/common-parameters.md Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Signed-off-by: Andrew Ross * Update _api-reference/common-parameters.md Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Signed-off-by: Andrew Ross * Update _api-reference/common-parameters.md Co-authored-by: Nathan Bower Signed-off-by: Andrew Ross * Update _api-reference/common-parameters.md Co-authored-by: Nathan Bower Signed-off-by: Andrew Ross --------- Signed-off-by: Andrew Ross Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Nathan Bower --- _api-reference/common-parameters.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/_api-reference/common-parameters.md b/_api-reference/common-parameters.md index 5b536ad992..ac3efbf4bf 100644 --- a/_api-reference/common-parameters.md +++ b/_api-reference/common-parameters.md @@ -123,4 +123,17 @@ Kilometers | `km` or `kilometers` Meters | `m` or `meters` Centimeters | `cm` or `centimeters` Millimeters | `mm` or `millimeters` -Nautical miles | `NM`, `nmi`, or `nauticalmiles` \ No newline at end of file +Nautical miles | `NM`, `nmi`, or `nauticalmiles` + +## `X-Opaque-Id` header + +You can specify an opaque identifier for any request using the `X-Opaque-Id` header. This identifier is used to track tasks and deduplicate deprecation warnings in server-side logs. This identifier is used to differentiate between callers sending requests to your OpenSearch cluster. Do not specify a unique value per request. + +#### Example request + +The following request adds an opaque ID to the request: + +```json +curl -H "X-Opaque-Id: my-curl-client-1" -XGET localhost:9200/_tasks +``` +{% include copy.html %} From e35c7bba54837f0853dbef992475c5835f25f39c Mon Sep 17 00:00:00 2001 From: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Date: Wed, 4 Dec 2024 09:49:59 -0500 Subject: [PATCH 32/69] Add word delimiter token filter documentation (#8871) Signed-off-by: Fanit Kolchina --- _analyzers/token-filters/index.md | 2 +- _analyzers/token-filters/word-delimiter.md | 127 +++++++++++++++++++++ 2 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 _analyzers/token-filters/word-delimiter.md diff --git a/_analyzers/token-filters/index.md b/_analyzers/token-filters/index.md index ed2c3ab349..b06489c805 100644 --- a/_analyzers/token-filters/index.md +++ b/_analyzers/token-filters/index.md @@ -63,5 +63,5 @@ Token filter | Underlying Lucene token filter| Description [`truncate`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/truncate/) | [TruncateTokenFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/TruncateTokenFilter.html) | Truncates tokens with lengths exceeding the specified character limit. [`unique`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/unique/) | N/A | Ensures that each token is unique by removing duplicate tokens from a stream. [`uppercase`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/uppercase/) | [UpperCaseFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/core/LowerCaseFilter.html) | Converts tokens to uppercase. -`word_delimiter` | [WordDelimiterFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/WordDelimiterFilter.html) | Splits tokens at non-alphanumeric characters and performs normalization based on the specified rules. +[`word_delimiter`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/word-delimiter/) | [WordDelimiterFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/WordDelimiterFilter.html) | Splits tokens at non-alphanumeric characters and performs normalization based on the specified rules. [`word_delimiter_graph`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/word-delimiter-graph/) | [WordDelimiterGraphFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/WordDelimiterGraphFilter.html) | Splits tokens at non-alphanumeric characters and performs normalization based on the specified rules. Assigns a `positionLength` attribute to multi-position tokens. diff --git a/_analyzers/token-filters/word-delimiter.md b/_analyzers/token-filters/word-delimiter.md new file mode 100644 index 0000000000..3ef5d12024 --- /dev/null +++ b/_analyzers/token-filters/word-delimiter.md @@ -0,0 +1,127 @@ +--- +layout: default +title: Word delimiter +parent: Token filters +nav_order: 470 +--- + +# Word delimiter token filter + +The `word_delimiter` token filter is used to split tokens at predefined characters and also offers optional token normalization based on customizable rules. + +We recommend using the `word_delimiter_graph` filter instead of the `word_delimiter` filter whenever possible because the `word_delimiter` filter sometimes produces invalid token graphs. For more information about the differences between the two filters, see [Differences between the `word_delimiter_graph` and `word_delimiter` filters]({{site.url}}{{site.baseurl}}/analyzers/token-filters/word-delimiter-graph/#differences-between-the-word_delimiter_graph-and-word_delimiter-filters). + +The `word_delimiter` filter is used to remove punctuation from complex identifiers like part numbers or product IDs. In such cases, it is best used with the `keyword` tokenizer. For hyphenated words, use the `synonym_graph` token filter instead of the `word_delimiter` filter because users frequently search for these terms both with and without hyphens. +{: .note} + +By default, the filter applies the following rules. + +| Description | Input | Output | +|:---|:---|:---| +| Treats non-alphanumeric characters as delimiters. | `ultra-fast` | `ultra`, `fast` | +| Removes delimiters at the beginning or end of tokens. | `Z99++'Decoder'`| `Z99`, `Decoder` | +| Splits tokens when there is a transition between uppercase and lowercase letters. | `OpenSearch` | `Open`, `Search` | +| Splits tokens when there is a transition between letters and numbers. | `T1000` | `T`, `1000` | +| Removes the possessive ('s) from the end of tokens. | `John's` | `John` | + +It's important **not** to use tokenizers that strip punctuation, like the `standard` tokenizer, with this filter. Doing so may prevent proper token splitting and interfere with options like `catenate_all` or `preserve_original`. We recommend using this filter with a `keyword` or `whitespace` tokenizer. +{: .important} + +## Parameters + +You can configure the `word_delimiter` token filter using the following parameters. + +Parameter | Required/Optional | Data type | Description +:--- | :--- | :--- | :--- +`catenate_all` | Optional | Boolean | Produces concatenated tokens from a sequence of alphanumeric parts. For example, `"quick-fast-200"` becomes `[ quickfast200, quick, fast, 200 ]`. Default is `false`. +`catenate_numbers` | Optional | Boolean | Concatenates numerical sequences. For example, `"10-20-30"` becomes `[ 102030, 10, 20, 30 ]`. Default is `false`. +`catenate_words` | Optional | Boolean | Concatenates alphabetic words. For example, `"high-speed-level"` becomes `[ highspeedlevel, high, speed, level ]`. Default is `false`. +`generate_number_parts` | Optional | Boolean | If `true`, numeric tokens (tokens consisting of numbers only) are included in the output. Default is `true`. +`generate_word_parts` | Optional | Boolean | If `true`, alphabetical tokens (tokens consisting of alphabetic characters only) are included in the output. Default is `true`. +`preserve_original` | Optional | Boolean | Keeps the original token (which may include non-alphanumeric delimiters) alongside the generated tokens in the output. For example, `"auto-drive-300"` becomes `[ auto-drive-300, auto, drive, 300 ]`. If `true`, the filter generates multi-position tokens not supported by indexing, so do not use this filter in an index analyzer or use the `flatten_graph` filter after this filter. Default is `false`. +`protected_words` | Optional | Array of strings | Specifies tokens that should not be split. +`protected_words_path` | Optional | String | Specifies a path (absolute or relative to the config directory) to a file containing tokens that should not be separated by new lines. +`split_on_case_change` | Optional | Boolean | Splits tokens where consecutive letters have different cases (one is lowercase and the other is uppercase). For example, `"OpenSearch"` becomes `[ Open, Search ]`. Default is `true`. +`split_on_numerics` | Optional | Boolean | Splits tokens where there are consecutive letters and numbers. For example `"v8engine"` will become `[ v, 8, engine ]`. Default is `true`. +`stem_english_possessive` | Optional | Boolean | Removes English possessive endings, such as `'s`. Default is `true`. +`type_table` | Optional | Array of strings | A custom map that specifies how to treat characters and whether to treat them as delimiters, which avoids unwanted splitting. For example, to treat a hyphen (`-`) as an alphanumeric character, specify `["- => ALPHA"]` so that words are not split at hyphens. Valid types are:
- `ALPHA`: alphabetical
- `ALPHANUM`: alphanumeric
- `DIGIT`: numeric
- `LOWER`: lowercase alphabetical
- `SUBWORD_DELIM`: non-alphanumeric delimiter
- `UPPER`: uppercase alphabetical +`type_table_path` | Optional | String | Specifies a path (absolute or relative to the config directory) to a file containing a custom character map. The map specifies how to treat characters and whether to treat them as delimiters, which avoids unwanted splitting. For valid types, see `type_table`. + +## Example + +The following example request creates a new index named `my-custom-index` and configures an analyzer with a `word_delimiter` filter: + +```json +PUT /my-custom-index +{ + "settings": { + "analysis": { + "analyzer": { + "custom_analyzer": { + "tokenizer": "keyword", + "filter": [ "custom_word_delimiter_filter" ] + } + }, + "filter": { + "custom_word_delimiter_filter": { + "type": "word_delimiter", + "split_on_case_change": true, + "split_on_numerics": true, + "stem_english_possessive": true + } + } + } + } +} +``` +{% include copy-curl.html %} + +## Generated tokens + +Use the following request to examine the tokens generated using the analyzer: + +```json +GET /my-custom-index/_analyze +{ + "analyzer": "custom_analyzer", + "text": "FastCar's Model2023" +} +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + { + "token": "Fast", + "start_offset": 0, + "end_offset": 4, + "type": "word", + "position": 0 + }, + { + "token": "Car", + "start_offset": 4, + "end_offset": 7, + "type": "word", + "position": 1 + }, + { + "token": "Model", + "start_offset": 10, + "end_offset": 15, + "type": "word", + "position": 2 + }, + { + "token": "2023", + "start_offset": 15, + "end_offset": 19, + "type": "word", + "position": 3 + } + ] +} +``` From 05d4125bbb6ab34ab68a6664bbad3c42f7264c1b Mon Sep 17 00:00:00 2001 From: Doo Yong Kim <0ctopus13prime@gmail.com> Date: Wed, 4 Dec 2024 07:01:23 -0800 Subject: [PATCH 33/69] Removing outdated limitations in Searchable Snapshots describing KNN is not compatible. (#8817) * Removing outdated limitations in Searchable Snapshots describing KNN is not compatible. Since 2.18, searchable snapshots is available with KNN Signed-off-by: Dooyong Kim * Update _tuning-your-cluster/availability-and-recovery/snapshots/searchable_snapshot.md Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Signed-off-by: Doo Yong Kim <0ctopus13prime@gmail.com> --------- Signed-off-by: Dooyong Kim Signed-off-by: Doo Yong Kim <0ctopus13prime@gmail.com> Co-authored-by: Dooyong Kim Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --- .../availability-and-recovery/snapshots/searchable_snapshot.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/_tuning-your-cluster/availability-and-recovery/snapshots/searchable_snapshot.md b/_tuning-your-cluster/availability-and-recovery/snapshots/searchable_snapshot.md index d13955f3f0..7076c792e2 100644 --- a/_tuning-your-cluster/availability-and-recovery/snapshots/searchable_snapshot.md +++ b/_tuning-your-cluster/availability-and-recovery/snapshots/searchable_snapshot.md @@ -46,7 +46,7 @@ services: - node.search.cache.size=50gb ``` - +- Starting with version 2.18, k-NN indexes support searchable snapshots for the NMSLIB and Faiss engines. ## Create a searchable snapshot index @@ -109,4 +109,3 @@ The following are known limitations of the searchable snapshots feature: - Searching remote data can impact the performance of other queries running on the same node. We recommend that users provision dedicated nodes with the `search` role for performance-critical applications. - For better search performance, consider [force merging]({{site.url}}{{site.baseurl}}/api-reference/index-apis/force-merge/) indexes into a smaller number of segments before taking a snapshot. For the best performance, at the cost of using compute resources prior to snapshotting, force merge your index into one segment. - We recommend configuring a maximum ratio of remote data to local disk cache size using the `cluster.filecache.remote_data_ratio` setting. A ratio of 5 is a good starting point for most workloads to ensure good query performance. If the ratio is too large, then there may not be sufficient disk space to handle the search workload. For more details on the maximum ratio of remote data, see issue [#11676](https://github.com/opensearch-project/OpenSearch/issues/11676). -- k-NN native-engine-based indexes using `faiss` and `nmslib` engines are incompatible with searchable snapshots. From d413869b5e34c5a11ef330e846006a6384e124c7 Mon Sep 17 00:00:00 2001 From: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Date: Wed, 4 Dec 2024 12:28:11 -0500 Subject: [PATCH 34/69] Delete ruby version file (#8879) Signed-off-by: Fanit Kolchina --- .ruby-version | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .ruby-version diff --git a/.ruby-version b/.ruby-version deleted file mode 100644 index 4772543317..0000000000 --- a/.ruby-version +++ /dev/null @@ -1 +0,0 @@ -3.3.2 From a59883742fd3e9b69adb139a15ff66ed2e7c516f Mon Sep 17 00:00:00 2001 From: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Date: Wed, 4 Dec 2024 12:28:34 -0500 Subject: [PATCH 35/69] Reorder two token filters alphabetically (#8880) Signed-off-by: Fanit Kolchina --- _analyzers/token-filters/synonym.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_analyzers/token-filters/synonym.md b/_analyzers/token-filters/synonym.md index a6865b14d7..296d5cd5db 100644 --- a/_analyzers/token-filters/synonym.md +++ b/_analyzers/token-filters/synonym.md @@ -2,7 +2,7 @@ layout: default title: Synonym parent: Token filters -nav_order: 420 +nav_order: 415 --- # Synonym token filter From a950b205898a9988aa8a2140e789a5e4c1a7e41e Mon Sep 17 00:00:00 2001 From: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Date: Wed, 4 Dec 2024 14:28:05 -0500 Subject: [PATCH 36/69] Update word-delimiter.md (#8883) Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --- _analyzers/token-filters/word-delimiter.md | 1 + 1 file changed, 1 insertion(+) diff --git a/_analyzers/token-filters/word-delimiter.md b/_analyzers/token-filters/word-delimiter.md index 3ef5d12024..d820fae2a0 100644 --- a/_analyzers/token-filters/word-delimiter.md +++ b/_analyzers/token-filters/word-delimiter.md @@ -10,6 +10,7 @@ nav_order: 470 The `word_delimiter` token filter is used to split tokens at predefined characters and also offers optional token normalization based on customizable rules. We recommend using the `word_delimiter_graph` filter instead of the `word_delimiter` filter whenever possible because the `word_delimiter` filter sometimes produces invalid token graphs. For more information about the differences between the two filters, see [Differences between the `word_delimiter_graph` and `word_delimiter` filters]({{site.url}}{{site.baseurl}}/analyzers/token-filters/word-delimiter-graph/#differences-between-the-word_delimiter_graph-and-word_delimiter-filters). +{: .important} The `word_delimiter` filter is used to remove punctuation from complex identifiers like part numbers or product IDs. In such cases, it is best used with the `keyword` tokenizer. For hyphenated words, use the `synonym_graph` token filter instead of the `word_delimiter` filter because users frequently search for these terms both with and without hyphens. {: .note} From 2ec6da70266dc58fa06f679bd5d87fe34c680572 Mon Sep 17 00:00:00 2001 From: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> Date: Wed, 4 Dec 2024 14:02:56 -0600 Subject: [PATCH 37/69] Fix number in Kubernetes page (#8878) Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> --- _tools/k8s-operator.md | 70 +++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/_tools/k8s-operator.md b/_tools/k8s-operator.md index 7ee1c1adee..5027dcf304 100644 --- a/_tools/k8s-operator.md +++ b/_tools/k8s-operator.md @@ -63,40 +63,40 @@ Then install the OpenSearch Kubernetes Operator using the following steps: 3. Enter `make build manifests`. 4. Start a Kubernetes cluster. When using minikube, open a new terminal window and enter `minikube start`. Kubernetes will now use a containerized minikube cluster with a namespace called `default`. Make sure that `~/.kube/config` points to the cluster. -```yml -apiVersion: v1 -clusters: -- cluster: - certificate-authority: /Users/naarcha/.minikube/ca.crt - extensions: - - extension: - last-update: Mon, 29 Aug 2022 10:11:47 CDT - provider: minikube.sigs.k8s.io - version: v1.26.1 - name: cluster_info - server: https://127.0.0.1:61661 - name: minikube -contexts: -- context: - cluster: minikube - extensions: - - extension: - last-update: Mon, 29 Aug 2022 10:11:47 CDT - provider: minikube.sigs.k8s.io - version: v1.26.1 - name: context_info - namespace: default - user: minikube - name: minikube -current-context: minikube -kind: Config -preferences: {} -users: -- name: minikube - user: - client-certificate: /Users/naarcha/.minikube/profiles/minikube/client.crt - client-key: /Users/naarcha/.minikube/profiles/minikube/client.key -``` + ```yml + apiVersion: v1 + clusters: + - cluster: + certificate-authority: /Users/naarcha/.minikube/ca.crt + extensions: + - extension: + last-update: Mon, 29 Aug 2022 10:11:47 CDT + provider: minikube.sigs.k8s.io + version: v1.26.1 + name: cluster_info + server: https://127.0.0.1:61661 + name: minikube + contexts: + - context: + cluster: minikube + extensions: + - extension: + last-update: Mon, 29 Aug 2022 10:11:47 CDT + provider: minikube.sigs.k8s.io + version: v1.26.1 + name: context_info + namespace: default + user: minikube + name: minikube + current-context: minikube + kind: Config + preferences: {} + users: + - name: minikube + user: + client-certificate: /Users/naarcha/.minikube/profiles/minikube/client.crt + client-key: /Users/naarcha/.minikube/profiles/minikube/client.key + ``` 5. Enter `make install` to create the CustomResourceDefinition that runs in your Kubernetes cluster. 6. Start the OpenSearch Kubernetes Operator. Enter `make run`. @@ -146,4 +146,4 @@ kubectl delete -f opensearch-cluster.yaml To learn more about how to customize your Kubernetes OpenSearch cluster, including data persistence, authentication methods, and scaling, see the [OpenSearch Kubernetes Operator User Guide](https://github.com/Opster/opensearch-k8s-operator/blob/main/docs/userguide/main.md). -If you want to contribute to the development of the OpenSearch Kubernetes Operator, see the repo [design documents](https://github.com/Opster/opensearch-k8s-operator/blob/main/docs/designs/high-level.md). \ No newline at end of file +If you want to contribute to the development of the OpenSearch Kubernetes Operator, see the repo [design documents](https://github.com/Opster/opensearch-k8s-operator/blob/main/docs/designs/high-level.md). From 866405197e8f656ec82115c5f2e6569c77d16a07 Mon Sep 17 00:00:00 2001 From: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> Date: Wed, 4 Dec 2024 17:46:53 -0600 Subject: [PATCH 38/69] Add migrations section (#8873) * Add migrations section. Signed-off-by: Archer * Add back upgrade pages. Make them less migration focused. Signed-off-by: Archer * Add back in upgrade pages. Signed-off-by: Archer * Remove upgrade section. Add redirects. Signed-off-by: Archer * Update migration-console-commands-references.md Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Implement doc review. Signed-off-by: Archer * Fix getting started headers. Signed-off-by: Archer * Fix parent child relationship for Deploying migration assistant Signed-off-by: Archer * Update _migration-assistant/migration-phases/assessing-your-cluster-for-migration.md Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Additional feedback Signed-off-by: Archer * Add permalink for migration console Signed-off-by: Archer * Add redirects for index pages. Signed-off-by: Archer * Final edits. Signed-off-by: Archer * Fix headings. Signed-off-by: Archer * Add copy buttons. Signed-off-by: Archer * Last copy button. Signed-off-by: Archer --------- Signed-off-by: Archer Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> --- _config.yml | 23 ++- _data/top_nav.yml | 2 + _includes/cards.html | 8 +- _layouts/default.html | 2 + .../configuration-options.md | 13 +- ...d-security-groups-for-existing-clusters.md | 8 +- .../deploying-migration-assistant/index.md | 6 +- .../getting-started-data-migration.md | 53 +++++-- .../index.md | 9 +- .../is-migration-assistant-right-for-you.md | 1 + .../accessing-the-migration-console.md | 3 +- .../migration-console/index.md | 5 + .../migration-console-commands-references.md | 17 ++- .../assessing-your-cluster-for-migration.md | 48 ++++++ .../migration-phases/backfill.md | 15 +- .../migration-phases/index.md | 7 +- .../migration-phases/migrating-metadata.md | 24 ++- .../removing-migration-infrastructure.md | 3 +- ...itching-traffic-from-the-source-cluster.md | 2 +- .../using-traffic-replayer.md | 6 +- .../verifying-migration-tools.md | 13 +- .../assessing-your-cluster-for-migration.md | 44 ------ .../load-sample-data-into-cluster.md | 144 ------------------ .../migration-timelines.md | 105 ------------- ...provisioning-source-cluster-for-testing.md | 91 ----------- _upgrade-to/index.md | 13 +- _upgrade-to/upgrade-to.md | 4 + .../migrations-architecture-overview.png | Bin 0 -> 360344 bytes 28 files changed, 216 insertions(+), 453 deletions(-) rename {_migrations => _migration-assistant}/deploying-migration-assistant/configuration-options.md (97%) rename {_migrations => _migration-assistant}/deploying-migration-assistant/iam-and-security-groups-for-existing-clusters.md (91%) rename {_migrations => _migration-assistant}/deploying-migration-assistant/index.md (58%) rename {_migrations => _migration-assistant}/getting-started-data-migration.md (86%) rename {_migrations => _migration-assistant}/index.md (95%) rename {_migrations => _migration-assistant}/is-migration-assistant-right-for-you.md (99%) rename {_migrations => _migration-assistant}/migration-console/accessing-the-migration-console.md (97%) rename {_migrations => _migration-assistant}/migration-console/index.md (85%) rename {_migrations => _migration-assistant}/migration-console/migration-console-commands-references.md (93%) create mode 100644 _migration-assistant/migration-phases/assessing-your-cluster-for-migration.md rename {_migrations => _migration-assistant}/migration-phases/backfill.md (93%) rename {_migrations => _migration-assistant}/migration-phases/index.md (57%) rename {_migrations => _migration-assistant}/migration-phases/migrating-metadata.md (95%) rename {_migrations => _migration-assistant}/migration-phases/removing-migration-infrastructure.md (92%) rename {_migrations => _migration-assistant}/migration-phases/switching-traffic-from-the-source-cluster.md (97%) rename {_migrations => _migration-assistant}/migration-phases/using-traffic-replayer.md (97%) rename {_migrations => _migration-assistant}/migration-phases/verifying-migration-tools.md (96%) delete mode 100644 _migrations/migration-phases/assessing-your-cluster-for-migration.md delete mode 100644 _migrations/other-helpful-pages/load-sample-data-into-cluster.md delete mode 100644 _migrations/other-helpful-pages/migration-timelines.md delete mode 100644 _migrations/other-helpful-pages/provisioning-source-cluster-for-testing.md create mode 100644 images/migrations/migrations-architecture-overview.png diff --git a/_config.yml b/_config.yml index 0e45176320..3c6f737cc8 100644 --- a/_config.yml +++ b/_config.yml @@ -31,9 +31,6 @@ collections: install-and-configure: permalink: /:collection/:path/ output: true - upgrade-to: - permalink: /:collection/:path/ - output: true im-plugin: permalink: /:collection/:path/ output: true @@ -94,6 +91,9 @@ collections: data-prepper: permalink: /:collection/:path/ output: true + migration-assistant: + permalink: /:collection/:path/ + output: true tools: permalink: /:collection/:path/ output: true @@ -137,11 +137,6 @@ opensearch_collection: install-and-configure: name: Install and upgrade nav_fold: true - upgrade-to: - name: Migrate to OpenSearch - # nav_exclude: true - nav_fold: true - # search_exclude: true im-plugin: name: Managing Indexes nav_fold: true @@ -213,6 +208,12 @@ clients_collection: name: Clients nav_fold: true +migration_assistant_collection: + collections: + migration-assistant: + name: Migration Assistant + nav_fold: true + benchmark_collection: collections: benchmark: @@ -252,6 +253,12 @@ defaults: values: section: "benchmark" section-name: "Benchmark" + - + scope: + path: "_migration-assistant" + values: + section: "migration-assistant" + section-name: "Migration Assistant" # Enable or disable the site search # By default, just-the-docs enables its JSON file-based search. We also have an OpenSearch-driven search functionality. diff --git a/_data/top_nav.yml b/_data/top_nav.yml index 51d8138680..6552d90359 100644 --- a/_data/top_nav.yml +++ b/_data/top_nav.yml @@ -63,6 +63,8 @@ items: url: /docs/latest/clients/ - label: Benchmark url: /docs/latest/benchmark/ + - label: Migration Assistant + url: /docs/latest/migration-assistant/ - label: Platform url: /platform/index.html children: diff --git a/_includes/cards.html b/_includes/cards.html index 6d958e61a5..5ab37b8c27 100644 --- a/_includes/cards.html +++ b/_includes/cards.html @@ -30,8 +30,14 @@

Measure performance metrics for your OpenSearch cluster

+ +
+ +

Migration Assistant

+

Migrate to OpenSearch from other platforms

+ +
- diff --git a/_layouts/default.html b/_layouts/default.html index d4d40d8cc4..7f2bf0a2a8 100755 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -87,6 +87,8 @@ {% assign section = site.clients_collection.collections %} {% elsif page.section == "benchmark" %} {% assign section = site.benchmark_collection.collections %} + {% elsif page.section == "migration-assistant" %} + {% assign section = site.migration_assistant_collection.collections %} {% endif %} {% if section %} diff --git a/_migrations/deploying-migration-assistant/configuration-options.md b/_migration-assistant/deploying-migration-assistant/configuration-options.md similarity index 97% rename from _migrations/deploying-migration-assistant/configuration-options.md rename to _migration-assistant/deploying-migration-assistant/configuration-options.md index 2e7f43e1b5..7097d7e90e 100644 --- a/_migrations/deploying-migration-assistant/configuration-options.md +++ b/_migration-assistant/deploying-migration-assistant/configuration-options.md @@ -2,7 +2,7 @@ layout: default title: Configuration options nav_order: 15 -parent: Deploying migration assistant +parent: Deploying Migration Assistant --- # Configuration options @@ -61,6 +61,7 @@ The following CDK performs a backfill migrations using RFS: } } ``` +{% include copy.html %} Performing an RFS backfill migration requires an existing snapshot. @@ -104,6 +105,7 @@ The following sample CDK performs a live capture migration with C&R: } } ``` +{% include copy.html %} Performing a live capture migration requires that a Capture Proxy be configured to capture incoming traffic and send it to the target cluster using the Traffic Replayer service. For arguments available in `captureProxyExtraArgs`, refer to the `@Parameter` fields [here](https://github.com/opensearch-project/opensearch-migrations/blob/main/TrafficCapture/trafficCaptureProxyServer/src/main/java/org/opensearch/migrations/trafficcapture/proxyserver/CaptureProxy.java). For `trafficReplayerExtraArgs`, refer to the `@Parameter` fields [here](https://github.com/opensearch-project/opensearch-migrations/blob/main/TrafficCapture/trafficReplayer/src/main/java/org/opensearch/migrations/replay/TrafficReplayer.java). At a minimum, no extra arguments may be needed. @@ -125,17 +127,18 @@ Both the source and target cluster can use no authentication, authentication lim ### No authentication -``` +```json "sourceCluster": { "endpoint": , "version": "ES 7.10", "auth": {"type": "none"} } ``` +{% include copy.html %} ### Basic authentication -``` +```json "sourceCluster": { "endpoint": , "version": "ES 7.10", @@ -146,10 +149,11 @@ Both the source and target cluster can use no authentication, authentication lim } } ``` +{% include copy.html %} ### Signature Version 4 authentication -``` +```json "sourceCluster": { "endpoint": , "version": "ES 7.10", @@ -160,6 +164,7 @@ Both the source and target cluster can use no authentication, authentication lim } } ``` +{% include copy.html %} The `serviceSigningName` can be `es` for an Elasticsearch or OpenSearch domain, or `aoss` for an OpenSearch Serverless collection. diff --git a/_migrations/deploying-migration-assistant/iam-and-security-groups-for-existing-clusters.md b/_migration-assistant/deploying-migration-assistant/iam-and-security-groups-for-existing-clusters.md similarity index 91% rename from _migrations/deploying-migration-assistant/iam-and-security-groups-for-existing-clusters.md rename to _migration-assistant/deploying-migration-assistant/iam-and-security-groups-for-existing-clusters.md index 808de79689..331b99e1fa 100644 --- a/_migrations/deploying-migration-assistant/iam-and-security-groups-for-existing-clusters.md +++ b/_migration-assistant/deploying-migration-assistant/iam-and-security-groups-for-existing-clusters.md @@ -2,7 +2,7 @@ layout: default title: IAM and security groups for existing clusters nav_order: 20 -parent: Deploying migration assistant +parent: Deploying Migration Assistant --- # IAM and security groups for existing clusters @@ -33,7 +33,7 @@ For an OpenSearch Serverless Collection, you will need to configure both network The Collection should have a network policy that uses the `VPC` access type. This requires creating a VPC endpoint on the VPC used for the solution. The VPC endpoint should be configured for the private subnets of the VPC and should attach the `osClusterAccessSG` security group. 2. **Data Access Policy Configuration**: - The data access policy should grant permission to perform all [index operations](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/serverless-data-access.html#serverless-data-supported-permissions) ↗ (`aoss:*`) for all indexes in the Collection. The IAM task roles of the applicable Migration services (Traffic Replayer, Migration Console, Reindex-from-Snapshot) should be used as the principals for this data access policy. + The data access policy should grant permission to perform all [index operations](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/serverless-data-access.html#serverless-data-supported-permissions) (`aoss:*`) for all indexes in the Collection. The IAM task roles of the applicable Migration services (Traffic Replayer, migration console, `Reindex-from-Snapshot`) should be used as the principals for this data access policy. ## Capture Proxy on Coordinator Nodes of Source Cluster @@ -68,7 +68,3 @@ Before [setting up Capture Proxy instances](https://github.com/opensearch-projec ] } ``` - -## Related Links - -- [OpenSearch traffic capture setup] \ No newline at end of file diff --git a/_migrations/deploying-migration-assistant/index.md b/_migration-assistant/deploying-migration-assistant/index.md similarity index 58% rename from _migrations/deploying-migration-assistant/index.md rename to _migration-assistant/deploying-migration-assistant/index.md index 6e245aa5da..1c559a81b1 100644 --- a/_migrations/deploying-migration-assistant/index.md +++ b/_migration-assistant/deploying-migration-assistant/index.md @@ -1,7 +1,11 @@ --- layout: default title: Deploying Migration Assistant -nav_order: 10 +nav_order: 15 +has_children: true +permalink: /deploying-migration-assistant/ +redirect-from: + - /deploying-migration-assistant/index/ --- # Deploying Migration Assistant diff --git a/_migrations/getting-started-data-migration.md b/_migration-assistant/getting-started-data-migration.md similarity index 86% rename from _migrations/getting-started-data-migration.md rename to _migration-assistant/getting-started-data-migration.md index 035ddae323..4110f29edf 100644 --- a/_migrations/getting-started-data-migration.md +++ b/_migration-assistant/getting-started-data-migration.md @@ -2,18 +2,20 @@ layout: default title: Getting started with data migration nav_order: 10 +redirect_from: + - /upgrade-to/upgrade-to/ + - /upgrade-to/snapshot-migrate/ --- # Getting started with data migration This quickstart outlines how to deploy Migration Assistant for OpenSearch and execute an existing data migration using `Reindex-from-Snapshot` (RFS). It uses AWS for illustrative purposes. However, the steps can be modified for use with other cloud providers. - ## Prerequisites and assumptions Before using this quickstart, make sure you fulfill the following prerequisites: -* Verify that your migration path [is supported](https://opensearch.org/docs/latest/migrations/is-migration-assistant-right-for-you/#supported-migration-paths). Note that we test with the exact versions specified, but you should be able to migrate data on alternative minor versions as long as the major version is supported. +* Verify that your migration path [is supported]({{site.url}}{{site.baseurl}}/migration-assistant/is-migration-assistant-right-for-you/#migration-paths). Note that we test with the exact versions specified, but you should be able to migrate data on alternative minor versions as long as the major version is supported. * The source cluster must be deployed Amazon Simple Storage Service (Amazon S3) plugin. * The target cluster must be deployed. @@ -27,7 +29,7 @@ The steps in this guide assume the following: --- -## Step 1: Installing Bootstrap on an Amazon EC2 instance (~10 minutes) +## Step 1: Install Bootstrap on an Amazon EC2 instance (~10 minutes) To begin your migration, use the following steps to install a `bootstrap` box on an Amazon Elastic Compute Cloud (Amazon EC2) instance. The instance uses AWS CloudFormation to create and manage the stack. @@ -41,7 +43,7 @@ To begin your migration, use the following steps to install a `bootstrap` box on --- -## Step 2: Setting up Bootstrap instance access (~5 minutes) +## Step 2: Set up Bootstrap instance access (~5 minutes) Use the following steps to set up Bootstrap instance access: @@ -63,12 +65,13 @@ Use the following steps to set up Bootstrap instance access: ] } ``` + {% include copy.html %} 3. Name the policy, for example, `SSM-OSMigrationBootstrapAccess`, and then create the policy by selecting **Create policy**. --- -## Step 3: Logging in to Bootstrap and building Migration Assistant (~15 minutes) +## Step 3: Log in to Bootstrap and building Migration Assistant (~15 minutes) Next, log in to Bootstrap and build Migration Assistant using the following steps. @@ -87,18 +90,20 @@ To use these steps, make sure you fulfill the following prerequisites: ```bash aws ssm start-session --document-name BootstrapShellDoc-- --target --region [--profile ] ``` + {% include copy.html %} 3. Once logged in, run the following command from the shell of the Bootstrap instance in the `/opensearch-migrations` directory: ```bash ./initBootstrap.sh && cd deployment/cdk/opensearch-service-migration ``` + {% include copy.html %} 4. After a successful build, note the path for infrastructure deployment, which will be used in the next step. --- -## Step 4: Configuring and deploying RFS (~20 minutes) +## Step 4: Configure and deploy RFS (~20 minutes) Use the following steps to configure and deploy RFS: @@ -134,6 +139,7 @@ Use the following steps to configure and deploy RFS: } } ``` + {% include copy.html %} The source and target cluster authorization can be configured to have no authorization, `basic` with a username and password, or `sigv4`. @@ -142,12 +148,14 @@ Use the following steps to configure and deploy RFS: ```bash cdk bootstrap --c contextId=migration-assistant --require-approval never ``` + {% include copy.html %} 4. Deploy the stacks: ```bash cdk deploy "*" --c contextId=migration-assistant --require-approval never --concurrency 5 ``` + {% include copy.html %} 5. Verify that all CloudFormation stacks were installed successfully. @@ -163,7 +171,7 @@ You will also need to give the `migrationconsole` and `reindexFromSnapshot` Task --- -## Step 5: Deploying Migration Assistant +## Step 5: Deploy Migration Assistant To deploy Migration Assistant, use the following steps: @@ -172,11 +180,14 @@ To deploy Migration Assistant, use the following steps: ```bash cdk bootstrap --c contextId=migration-assistant --require-approval never --concurrency 5 ``` + {% include copy.html %} + 2. Deploy the stacks when `cdk.context.json` is fully configured: ```bash cdk deploy "*" --c contextId=migration-assistant --require-approval never --concurrency 3 ``` + {% include copy.html %} These commands deploy the following stacks: @@ -186,13 +197,14 @@ These commands deploy the following stacks: --- -## Step 6: Accessing the migration console +## Step 6: Access the migration console Run the following command to access the migration console: ```bash ./accessContainer.sh migration-console dev ``` +{% include copy.html %} `accessContainer.sh` is located in `/opensearch-migrations/deployment/cdk/opensearch-service-migration/` on the Bootstrap instance. To learn more, see [Accessing the migration console]. @@ -200,17 +212,18 @@ Run the following command to access the migration console: --- -## Step 7: Verifying the connection to the source and target clusters +## Step 7: Verify the connection to the source and target clusters To verify the connection to the clusters, run the following command: ```bash console clusters connection-check ``` +{% include copy.html %} You should receive the following output: -``` +```bash * **Source Cluster:** Successfully connected! * **Target Cluster:** Successfully connected! ``` @@ -219,25 +232,28 @@ To learn more about migration console commands, see [Migration commands]. --- -## Step 8: Snapshot creation +## Step 8: Create a snapshot Run the following command to initiate snapshot creation from the source cluster: ```bash console snapshot create [...] ``` +{% include copy.html %} To check the snapshot creation status, run the following command: ```bash console snapshot status [...] ``` +{% include copy.html %} To learn more information about the snapshot, run the following command: ```bash console snapshot status --deep-check [...] ``` +{% include copy.html %} Wait for snapshot creation to complete before moving to step 9. @@ -245,19 +261,20 @@ To learn more about snapshot creation, see [Snapshot Creation]. --- -## Step 9: Metadata migration +## Step 9: Migrate metadata Run the following command to migrate metadata: ```bash console metadata migrate [...] ``` +{% include copy.html %} -For more information, see [Migrating metadata]({{site.url}}{{site.baseurl}}/migrations/migration-phases/migrating-metadata/). +For more information, see [Migrating metadata]({{site.url}}{{site.baseurl}}/migration-assistant/migration-phases/migrating-metadata/). --- -## Step 10: RFS document migration +## Step 10: Migrate documents with RFS You can now use RFS to migrate documents from your original cluster: @@ -266,26 +283,30 @@ You can now use RFS to migrate documents from your original cluster: ```bash console backfill start ``` + {% include copy.html %} 2. _(Optional)_ To speed up the migration, increase the number of documents processed at a simultaneously by using the following command: ```bash console backfill scale ``` + {% include copy.html %} 3. To check the status of the documentation backfill, use the following command: ```bash console backfill status ``` + {% include copy.html %} 4. If you need to stop the backfill process, use the following command: ```bash console backfill stop ``` + {% include copy.html %} -For more information, see [Backfill]({{site.url}}{{site.baseurl}}/migrations/migration-phases/backfill/). +For more information, see [Backfill]({{site.url}}{{site.baseurl}}/migration-assistant/migration-phases/backfill/). --- @@ -296,6 +317,7 @@ Use the following command for detailed monitoring of the backfill process: ```bash console backfill status --deep-check ``` +{% include copy.html %} You should receive the following output: @@ -325,6 +347,7 @@ fields @message | sort @timestamp desc | limit 10000 ``` +{% include copy.html %} If any failed documents are identified, you can index the failed documents directly as opposed to using RFS. diff --git a/_migrations/index.md b/_migration-assistant/index.md similarity index 95% rename from _migrations/index.md rename to _migration-assistant/index.md index e3b2657e1a..f024fdb69c 100644 --- a/_migrations/index.md +++ b/_migration-assistant/index.md @@ -5,6 +5,11 @@ nav_order: 1 has_children: false nav_exclude: true has_toc: false +permalink: /migration-assistant/ +redirect_from: + - /migration-assistant/index/ + - /upgrade-to/index/ + - /upgrade-to/ --- # Migration Assistant for OpenSearch @@ -53,13 +58,13 @@ The Metadata migration tool integrated into the Migration CLI can be used indepe The destination cluster for migration or comparison in an A/B test. -### Architecture overview +## Architecture overview The Migration Assistant architecture is based on the use of an AWS Cloud infrastructure, but most tools are designed to be cloud independent. A local containerized version of this solution is also available. The design deployed in AWS is as follows: -![Migration architecture overview]({{site.url}}{{site.baseurl}}/images/migrations/migration-architecture-overview.svg) +![Migration architecture overview]({{site.url}}{{site.baseurl}}/images/migrations/migrations-architecture-overview.png) 1. Client traffic is directed to the existing cluster. 2. An Application Load Balancer with capture proxies relays traffic to a source while replicating data to Amazon Managed Streaming for Apache Kafka (Amazon MSK). diff --git a/_migrations/is-migration-assistant-right-for-you.md b/_migration-assistant/is-migration-assistant-right-for-you.md similarity index 99% rename from _migrations/is-migration-assistant-right-for-you.md rename to _migration-assistant/is-migration-assistant-right-for-you.md index 6a09e44206..073c2b6cd7 100644 --- a/_migrations/is-migration-assistant-right-for-you.md +++ b/_migration-assistant/is-migration-assistant-right-for-you.md @@ -30,6 +30,7 @@ There are also tools available for migrating cluster configuration, templates, a {: .note} ### Supported source and target platforms + * Self-managed (hosted by cloud provider or on-premises) * AWS OpenSearch diff --git a/_migrations/migration-console/accessing-the-migration-console.md b/_migration-assistant/migration-console/accessing-the-migration-console.md similarity index 97% rename from _migrations/migration-console/accessing-the-migration-console.md rename to _migration-assistant/migration-console/accessing-the-migration-console.md index d6cf9ec150..ea66f5c04c 100644 --- a/_migrations/migration-console/accessing-the-migration-console.md +++ b/_migration-assistant/migration-console/accessing-the-migration-console.md @@ -16,6 +16,7 @@ export STAGE=dev export AWS_REGION=us-west-2 /opensearch-migrations/deployment/cdk/opensearch-service-migration/accessContainer.sh migration-console ${STAGE} ${AWS_REGION} ``` +{% include copy.html %} When opening the console a message will appear above the command prompt, `Welcome to the Migration Assistant Console`. @@ -29,6 +30,6 @@ export SERVICE_NAME=migration-console export TASK_ARN=$(aws ecs list-tasks --cluster migration-${STAGE}-ecs-cluster --family "migration-${STAGE}-${SERVICE_NAME}" | jq --raw-output '.taskArns[0]') aws ecs execute-command --cluster "migration-${STAGE}-ecs-cluster" --task "${TASK_ARN}" --container "${SERVICE_NAME}" --interactive --command "/bin/bash" ``` - +{% include copy.html %} Typically, `STAGE` is equivalent to a standard `dev` environment, but this may vary based on what the user specified during deployment. \ No newline at end of file diff --git a/_migrations/migration-console/index.md b/_migration-assistant/migration-console/index.md similarity index 85% rename from _migrations/migration-console/index.md rename to _migration-assistant/migration-console/index.md index 7ebac65836..3e08e72c5c 100644 --- a/_migrations/migration-console/index.md +++ b/_migration-assistant/migration-console/index.md @@ -3,8 +3,13 @@ layout: default title: Migration console nav_order: 30 has_children: true +permalink: /migration-console/ +redirect_from: + - /migration-console/index/ --- +# Migration console + The Migrations Assistant deployment includes an Amazon Elastic Container Service (Amazon ECS) task that hosts tools that run different phases of the migration and check the progress or results of the migration. This ECS task is called the **migration console**. The migration console is a command line interface used to interact with the deployed components of the solution. This section provides information about how to access the migration console and what commands are supported. diff --git a/_migrations/migration-console/migration-console-commands-references.md b/_migration-assistant/migration-console/migration-console-commands-references.md similarity index 93% rename from _migrations/migration-console/migration-console-commands-references.md rename to _migration-assistant/migration-console/migration-console-commands-references.md index 55731229e0..21d793b3f3 100644 --- a/_migrations/migration-console/migration-console-commands-references.md +++ b/_migration-assistant/migration-console/migration-console-commands-references.md @@ -1,11 +1,10 @@ --- layout: default title: Command reference -nav_order: 35 +nav_order: 40 parent: Migration console --- - # Migration console command reference Migration console commands follow this syntax: `console [component] [action]`. The components include `clusters`, `backfill`, `snapshot`, `metadata`, and `replay`. The console is configured with a registry of the deployed services and the source and target cluster, generated from the `cdk.context.json` values. @@ -21,6 +20,7 @@ Reports whether both the source and target clusters can be reached and provides ```sh console clusters connection-check ``` +{% include copy.html %} ### Run `cat-indices` @@ -29,6 +29,7 @@ Runs the `cat-indices` API on the cluster. ```sh console clusters cat-indices ``` +{% include copy.html %} ### Create a snapshot @@ -37,6 +38,7 @@ Creates a snapshot of the source cluster and stores it in a preconfigured Amazon ```sh console snapshot create ``` +{% include copy.html %} ## Check snapshot status @@ -45,6 +47,7 @@ Runs a detailed check on the snapshot creation status, including estimated compl ```sh console snapshot status --deep-check ``` +{% include copy.html %} ## Evaluate metadata @@ -53,6 +56,7 @@ Performs a dry run of metadata migration, showing which indexes, templates, and ```sh console metadata evaluate ``` +{% include copy.html %} ## Migrate metadata @@ -61,6 +65,7 @@ Migrates the metadata from the source cluster to the target cluster. ```sh console metadata migrate ``` +{% include copy.html %} ## Start a backfill @@ -72,6 +77,7 @@ There are similar `scale UNITS` and `stop` commands to change the number of acti ```sh console backfill start ``` +{% include copy.html %} ## Check backfill status @@ -86,6 +92,7 @@ The `stop` command stops all active instances. ```sh console replay start ``` +{% include copy.html %} ## Read logs @@ -94,9 +101,9 @@ Reads any logs that exist when running Traffic Replayer. Use tab completion on t ```sh console tuples show --in /shared-logs-output/traffic-replayer-default/[NODE_ID]/tuples/console.log | jq > readable_tuples.json ``` +{% include copy.html %} - -## Help command +## Help option All commands and options can be explored within the tool itself by using the `--help` option, either for the entire `console` application or for individual components (for example, `console backfill --help`). For example: @@ -121,4 +128,4 @@ Commands: replay Commands related to controlling the replayer. snapshot Commands to create and check status of snapshots of the... tuples All commands related to tuples. -``` \ No newline at end of file +``` diff --git a/_migration-assistant/migration-phases/assessing-your-cluster-for-migration.md b/_migration-assistant/migration-phases/assessing-your-cluster-for-migration.md new file mode 100644 index 0000000000..5ded49eb59 --- /dev/null +++ b/_migration-assistant/migration-phases/assessing-your-cluster-for-migration.md @@ -0,0 +1,48 @@ +--- +layout: default +title: Assessing your cluster for migration +nav_order: 60 +parent: Migration phases +--- + +# Assessing your cluster for migration + + +The goal of the Migration Assistant is to streamline the process of migrating from one location or version of Elasticsearch/OpenSearch to another. However, completing a migration sometimes requires resolving client compatibility issues before they can communicate directly with the target cluster. + +## Understanding breaking changes + +Before performing any upgrade or migration, you should review any documentation of breaking changes. Even if the cluster is migrated there might be changes required for clients to connect to the new cluster + +## Upgrade and breaking changes guides + +For migrations paths between Elasticsearch 6.8 and OpenSearch 2.x users should be familiar with documentation in the links below that apply to their specific case: + +* [Upgrading Amazon Service Domains](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/version-migration.html). + +* [Changes from Elasticsearch to OpenSearch fork](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/rename.html). + +* [OpenSearch Breaking Changes](https://opensearch.org/docs/latest/breaking-changes/). + +The next step is to set up a proper test bed to verify that your applications will work as expected on the target version. + +## Impact of data transformations + +Any time you apply a transformation to your data, such as: + +- Changing index names +- Modifying field names or field mappings +- Splitting indices with type mappings + +These changes might need to be reflected in your client configurations. For example, if your clients are reliant on specific index or field names, you must ensure that their queries are updated accordingly. + +We recommend running production-like queries against the target cluster before switching over actual production traffic. This helps verify that the client can: + +- Communicate with the target cluster +- Locate the necessary indices and fields +- Retrieve the expected results + +For complex migrations involving multiple transformations or breaking changes, we highly recommend performing a trial migration with representative, non-production data (e.g., in a staging environment) to fully test client compatibility with the target cluster. + + + diff --git a/_migrations/migration-phases/backfill.md b/_migration-assistant/migration-phases/backfill.md similarity index 93% rename from _migrations/migration-phases/backfill.md rename to _migration-assistant/migration-phases/backfill.md index ccdbadd042..d2ff7cd873 100644 --- a/_migrations/migration-phases/backfill.md +++ b/_migration-assistant/migration-phases/backfill.md @@ -7,7 +7,7 @@ parent: Migration phases # Backfill -After the [metadata]({{site.url}}{{site.baseurl}}/migrations/migration-phases/migrating-metadata/) for your cluster has been migrated, you can use capture proxy data replication and snapshots to backfill your data into the next cluster. +After the [metadata]({{site.url}}{{site.baseurl}}/migration-assistant/migration-phases/migrating-metadata/) for your cluster has been migrated, you can use capture proxy data replication and snapshots to backfill your data into the next cluster. ## Capture proxy data replication @@ -48,15 +48,16 @@ If you're only using backfill as your migration technique, make a client/DNS cha After you have routed the client based on your use case, test adding records against HTTP requests using the following steps: -1. In the migration console, run the following command: +In the migration console, run the following command: - ```shell + ```bash console kafka describe-topic-records ``` + {% include copy.html %} Note the records in the logging topic. -2. After a short period, execute the same command again and compare the increased number of records against the expected HTTP requests. +After a short period, execute the same command again and compare the increased number of records against the expected HTTP requests. ## Creating a snapshot @@ -66,12 +67,14 @@ Create a snapshot for your backfill using the following command: ```bash console snapshot create ``` +{% include copy.html %} To check the progress of your snapshot, use the following command: ```bash console snapshot status --deep-check ``` +{% include copy.html %} Depending on the size of the data in the source cluster and the bandwidth allocated for snapshots, the process can take some time. Adjust the maximum rate at which the source cluster's nodes create the snapshot using the `--max-snapshot-rate-mb-per-node` option. Increasing the snapshot rate will consume more node resources, which may affect the cluster's ability to handle normal traffic. @@ -86,6 +89,7 @@ You can check the indexes and document counts of the source and target clusters ```shell console clusters cat-indices ``` +{% include copy.html %} You should receive the following response: @@ -106,6 +110,7 @@ Use the following command to start the backfill and deploy the workers: ```shell console backfill start ``` +{% include copy.html %} You should receive a response similar to the following: @@ -130,6 +135,7 @@ To speed up the transfer, you can scale the number of workers. It may take a few ```shell console backfill scale 5 ``` +{% include copy.html %} We recommend slowly scaling up the fleet while monitoring the health metrics of the target cluster to avoid over-saturating it. [Amazon OpenSearch Service domains](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/monitoring.html) provide a number of metrics and logs that can provide this insight. @@ -154,6 +160,7 @@ After the backfill is complete and the workers have stopped, examine the content ```shell console clusters cat-indices --refresh ``` +{% include copy.html %} This will display the number of documents in each of the indexes in the target cluster, as shown in the following example response: diff --git a/_migrations/migration-phases/index.md b/_migration-assistant/migration-phases/index.md similarity index 57% rename from _migrations/migration-phases/index.md rename to _migration-assistant/migration-phases/index.md index b637d4a28d..c3c6c14b07 100644 --- a/_migrations/migration-phases/index.md +++ b/_migration-assistant/migration-phases/index.md @@ -3,11 +3,14 @@ layout: default title: Migration phases nav_order: 50 has_children: true +permalink: /migration-phases/ +redirect_from: + - /migration-phases/index/ --- This page details how to conduct a migration with Migration Assistant. It encompasses a variety of scenarios including: -- [**Metadata migration**]({{site.url}}{{site.baseurl}}/migrations/migration-phases/migrating-metadata/): Migrating cluster metadata, such as index settings, aliases, and templates. -- [**Backfill migration**]({{site.url}}{{site.baseurl}}/migrations/migration-phases/backfill/): Migrating existing or historical data from a source to a target cluster. +- [**Metadata migration**]({{site.url}}{{site.baseurl}}/migration-assistant/migration-phases/migrating-metadata/): Migrating cluster metadata, such as index settings, aliases, and templates. +- [**Backfill migration**]({{site.url}}{{site.baseurl}}/migration-assistant/migration-phases/backfill/): Migrating existing or historical data from a source to a target cluster. - **Live traffic migration**: Replicating live ongoing traffic from a source to a target cluster. diff --git a/_migrations/migration-phases/migrating-metadata.md b/_migration-assistant/migration-phases/migrating-metadata.md similarity index 95% rename from _migrations/migration-phases/migrating-metadata.md rename to _migration-assistant/migration-phases/migrating-metadata.md index 2a4079ca3f..249a2ca4d0 100644 --- a/_migrations/migration-phases/migrating-metadata.md +++ b/_migration-assistant/migration-phases/migrating-metadata.md @@ -22,12 +22,14 @@ Create the initial snapshot of the source cluster using the following command: ```shell console snapshot create ``` +{% include copy.html %} To check the progress of the snapshot in real time, use the following command: ```shell console snapshot status --deep-check ``` +{% include copy.html %} You should receive the following response when the snapshot is created: @@ -49,28 +51,28 @@ Throughput: 38.13 MiB/sec Depending on the size of the data in the source cluster and the bandwidth allocated for snapshots, the process can take some time. Adjust the maximum rate at which the source cluster's nodes create the snapshot using the `--max-snapshot-rate-mb-per-node` option. Increasing the snapshot rate will consume more node resources, which may affect the cluster's ability to handle normal traffic. -## Command Arguments +## Command arguments For the following commands, to identify all valid arguments, please run with `--help`. ```shell console metadata evaluate --help ``` +{% include copy.html %} ```shell console metadata migrate --help ``` +{% include copy.html %} Based on the migration console deployment options, a number of commands will be pre-populated. To view them, run console with verbosity: ```shell console -v metadata migrate --help ``` +{% include copy.html %} -
- -Example "console -v metadata migrate --help" command output - +You should receive a response similar to the following: ```shell (.venv) bash-5.2# console -v metadata migrate --help @@ -92,6 +94,7 @@ By scanning the contents of the source cluster, applying filtering, and applying ```shell console metadata evaluate [...] ``` +{% include copy.html %} You should receive a response similar to the following: @@ -131,6 +134,7 @@ Running through the same data as the evaluate command all of the migrated items ```shell console metadata migrate [...] ``` +{% include copy.html %} You should receive a response similar to the following: @@ -162,7 +166,7 @@ Migrated Items: Results: 0 issue(s) detected ``` -
+ ## Metadata verification process @@ -172,19 +176,21 @@ Before moving on to additional migration steps, it is recommended to confirm det Use these instructions to help troubleshoot the following issues. -### Access detailed logs +### Accessing detailed logs Metadata migration creates a detailed log file that includes low level tracing information for troubleshooting. For each execution of the program a log file is created inside a shared volume on the migration console named `shared-logs-output` the following command will list all log files, one for each run of the command. ```shell ls -al /shared-logs-output/migration-console-default/*/metadata/ ``` +{% include copy.html %} To inspect the file within the console `cat`, `tail` and `grep` commands line tools. By looking for warnings, errors and exceptions in this log file can help understand the source of failures, or at the very least be useful for creating issues in this project. ```shell tail /shared-logs-output/migration-console-default/*/metadata/*.log ``` +{% include copy.html %} ### Warnings and errors @@ -207,6 +213,7 @@ As Metadata migration supports migrating from ES 6.8 on to the latest versions o **Example starting state with mapping type foo (ES 6):** + ```json { "mappings": [ @@ -221,8 +228,10 @@ As Metadata migration supports migrating from ES 6.8 on to the latest versions o ] } ``` +{% include copy.html %} **Example ending state with foo removed (ES 7):** + ```json { "mappings": { @@ -233,5 +242,6 @@ As Metadata migration supports migrating from ES 6.8 on to the latest versions o } } ``` +{% include copy.html %} For additional technical details, [view the mapping type removal source code](https://github.com/opensearch-project/opensearch-migrations/blob/main/transformation/src/main/java/org/opensearch/migrations/transformation/rules/IndexMappingTypeRemoval.java). diff --git a/_migrations/migration-phases/removing-migration-infrastructure.md b/_migration-assistant/migration-phases/removing-migration-infrastructure.md similarity index 92% rename from _migrations/migration-phases/removing-migration-infrastructure.md rename to _migration-assistant/migration-phases/removing-migration-infrastructure.md index 75413f25f0..656a8e1998 100644 --- a/_migrations/migration-phases/removing-migration-infrastructure.md +++ b/_migration-assistant/migration-phases/removing-migration-infrastructure.md @@ -7,13 +7,14 @@ parent: Migration phases # Removing migration infrastructure -After a migration is complete all resources should be removed except for the target cluster, and optionally your Cloudwatch Logs, and Replayer logs. +After a migration is complete all resources should be removed except for the target cluster, and optionally your Cloudwatch Logs, and Traffic Replayer logs. To remove all the CDK stack(s) which get created during a deployment you can execute a command similar to below within the CDK directory ```bash cdk destroy "*" --c contextId= ``` +{% include copy.html %} Follow the instructions on the command-line to remove the deployed resources from the AWS account. diff --git a/_migrations/migration-phases/switching-traffic-from-the-source-cluster.md b/_migration-assistant/migration-phases/switching-traffic-from-the-source-cluster.md similarity index 97% rename from _migrations/migration-phases/switching-traffic-from-the-source-cluster.md rename to _migration-assistant/migration-phases/switching-traffic-from-the-source-cluster.md index c0fe834943..c43580eef9 100644 --- a/_migrations/migration-phases/switching-traffic-from-the-source-cluster.md +++ b/_migration-assistant/migration-phases/switching-traffic-from-the-source-cluster.md @@ -13,7 +13,7 @@ After the source and target clusters are synchronized, traffic needs to be switc This page assumes that the following has occurred before making the switch: -- All client traffic is being routed through a switchover listener in the [MigrationAssistant Application Load Balancer]({{site.url}}{{site.baseurl}}/migrations/migration-phases/backfill/). +- All client traffic is being routed through a switchover listener in the [MigrationAssistant Application Load Balancer]({{site.url}}{{site.baseurl}}/migration-assistant/migration-phases/backfill/). - Client traffic has been verified as compatible with the target cluster. - The target cluster is in a good state to accept client traffic. - The target proxy service is deployed. diff --git a/_migrations/migration-phases/using-traffic-replayer.md b/_migration-assistant/migration-phases/using-traffic-replayer.md similarity index 97% rename from _migrations/migration-phases/using-traffic-replayer.md rename to _migration-assistant/migration-phases/using-traffic-replayer.md index 1c812be211..5b7af3c3f7 100644 --- a/_migrations/migration-phases/using-traffic-replayer.md +++ b/_migration-assistant/migration-phases/using-traffic-replayer.md @@ -17,7 +17,7 @@ For example, if a document was deleted after a snapshot was taken, starting Traf ## Configuration options -[Traffic Replayer settings]({{site.url}}{{site.baseurl}}/migrations/deploying-migration-assisstant/configuation-options/) are configured during the deployment of Migration Assistant. Make sure to set the authentication mode for Traffic Replayer so that it can properly communicate with the target cluster. For more information about different types of traffic that are handled by Traffic Replayer, see [limitations](#limitations). +[Traffic Replayer settings]({{site.url}}{{site.baseurl}}/migration-assistant/deploying-migration-assistant/configuration-options/) are configured during the deployment of Migration Assistant. Make sure to set the authentication mode for Traffic Replayer so that it can properly communicate with the target cluster. ## Using Traffic Replayer @@ -152,12 +152,13 @@ Suppose that a source request contains a `tagToExcise` element that needs to be The resulting request sent to the target will appear similar to the following: -```http +```bash PUT /oldStyleIndex/moreStuff HTTP/1.0 host: testhostname {"top":{"properties":{"field1":{"type":"text"},"field2":{"type":"keyword"}}}} ``` +{% include copy.html %} You can pass Base64-encoded transformation scripts using `--transformer-config-base64`. @@ -220,6 +221,7 @@ The following example log entry shows a `/_cat/indices?v` request sent to both t "numErrors": 0 } ``` +{% include copy.html %} ### Decoding log content diff --git a/_migrations/migration-phases/verifying-migration-tools.md b/_migration-assistant/migration-phases/verifying-migration-tools.md similarity index 96% rename from _migrations/migration-phases/verifying-migration-tools.md rename to _migration-assistant/migration-phases/verifying-migration-tools.md index 498ed50feb..77df2b4280 100644 --- a/_migrations/migration-phases/verifying-migration-tools.md +++ b/_migration-assistant/migration-phases/verifying-migration-tools.md @@ -9,7 +9,7 @@ parent: Migration phases Before using the Migration Assistant, take the following steps to verify that your cluster is ready for migration. -## Snapshot creation verification +## Verifying snapshot creation Verify that a snapshot can be created of your source cluster and used for metadata and backfill scenarios. @@ -28,6 +28,7 @@ Create an S3 bucket for the snapshot using the following AWS Command Line Interf ```shell aws s3api create-bucket --bucket --region ``` +{% include copy.html %} Register a new S3 snapshot repository on your source cluster using the following cURL command: @@ -40,6 +41,7 @@ curl -X PUT "http://:9200/_snapshot/test_s3_repository" -H } }' ``` +{% include copy.html %} Next, create a test snapshot that captures only the cluster's metadata: @@ -50,6 +52,7 @@ curl -X PUT "http://:9200/_snapshot/test_s3_repository/test "include_global_state": true }' ``` +{% include copy.html %} Check the AWS Management Console to confirm that your bucket contains the snapshot. @@ -62,12 +65,14 @@ To remove the resources created during verification, you can use the following d ```shell curl -X DELETE "http://:9200/_snapshot/test_s3_repository/test_snapshot_1?pretty" ``` +{% include copy.html %} **Test snapshot repository** ```shell curl -X DELETE "http://:9200/_snapshot/test_s3_repository?pretty" ``` +{% include copy.html %} **S3 bucket** @@ -75,6 +80,7 @@ curl -X DELETE "http://:9200/_snapshot/test_s3_repository?p aws s3 rm s3:// --recursive aws s3api delete-bucket --bucket --region ``` +{% include copy.html %} ### Troubleshooting @@ -162,7 +168,7 @@ Look for failing tasks by navigating to **Traffic Capture Proxy ECS**. Change ** After all verifications are complete, reset all resources before using Migration Assistant for an actual migration. -The following steps outline how to reset resources with Migration Assistant before executing the actual migration. At this point all verifications are expected to have been completed. These steps can be performed after [Accessing the Migration Console]({{site.url}}{{site.baseurl}}/migrations/migration-console/accessing-the-migration-console/). +The following steps outline how to reset resources with Migration Assistant before executing the actual migration. At this point all verifications are expected to have been completed. These steps can be performed after [Accessing the Migration Console]({{site.url}}{{site.baseurl}}/migration-assistant/migration-console/accessing-the-migration-console/). ### Traffic Replayer @@ -171,6 +177,7 @@ To stop running Traffic Replayer, use the following command: ```bash console replay stop ``` +{% include copy.html %} ### Kafka @@ -182,6 +189,7 @@ This command will result in the loss of any traffic data captured by the capture ```bash console kafka delete-topic ``` +{% include copy.html %} ### Target cluster @@ -193,4 +201,5 @@ This command will result in the loss of all data in the target cluster and shoul ```bash console clusters clear-indices --cluster target ``` +{% include copy.html %} diff --git a/_migrations/migration-phases/assessing-your-cluster-for-migration.md b/_migrations/migration-phases/assessing-your-cluster-for-migration.md deleted file mode 100644 index d056754555..0000000000 --- a/_migrations/migration-phases/assessing-your-cluster-for-migration.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -layout: default -title: Assessing your cluster for migration -nav_order: 60 -has_children: true -parent: Migration phases ---- - -# Assessing your cluster for migration - -The goal of Migration Assistant is to streamline the process of migrating from one location or version of Elasticsearch/OpenSearch to another. However, completing a migration sometimes requires resolving client compatibility issues before they can communicate directly with the target cluster. - -## Understanding breaking changes - -Before performing any upgrade or migration, you should review any breaking changes documentation. Even if the cluster is migrated, there may be changes required in order for clients to connect to the new cluster. - -## Upgrade and breaking changes guides - -For migration paths between Elasticsearch 6.8 and OpenSearch 2.x, you should be familiar with the following documentation, depending on your specific use case: - -* [Upgrading Amazon OpenSearch Service domains](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/version-migration.html). - -* [Amazon OpenSearch Service rename - Summary of changes](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/rename.html). - -* [OpenSearch breaking changes](https://opensearch.org/docs/latest/breaking-changes/). - -The next step is to set up a proper test bed to verify that your applications will work as expected on the target version. - -## Impact of data transformations - -Any time you apply a transformation to your data, such as changing index names, modifying field names or field mappings, or splitting indexes with type mappings, these changes may need to be reflected in your client configurations. For example, if your clients are reliant on specific index or field names, you must ensure that their queries are updated accordingly. - - - -We recommend running production-like queries against the target cluster before switching to actual production traffic. This helps verify that the client can: - -- Communicate with the target cluster. -- Locate the necessary indexes and fields. -- Retrieve the expected results. - -For complex migrations involving multiple transformations or breaking changes, we highly recommend performing a trial migration with representative, non-production data (for example, in a staging environment) to fully test client compatibility with the target cluster. - - - diff --git a/_migrations/other-helpful-pages/load-sample-data-into-cluster.md b/_migrations/other-helpful-pages/load-sample-data-into-cluster.md deleted file mode 100644 index 6dbd9fdcb2..0000000000 --- a/_migrations/other-helpful-pages/load-sample-data-into-cluster.md +++ /dev/null @@ -1,144 +0,0 @@ - -This guide demonstrates how to quickly load test data into an Elasticsearch or OpenSearch source cluster using AWS Glue and the AWS Open Dataset library. We'll walk through indexing Bitcoin transaction data on the source cluster. For more details, refer to [the official AWS documentation on setting up Glue connections to OpenSearch](https://docs.aws.amazon.com/glue/latest/dg/aws-glue-programming-etl-connect-opensearch-home.html) ↗. - -## 1. Create Your Source Cluster - -Create your source cluster using the method of your choice, keeping in mind the following requirements: - -* We will use basic authentication (username/password) for access control. In this example, we are using Elasticsearch 7.10, but earlier versions of Elasticsearch and OpenSearch 1.X and 2.X are also supported. -* The source cluster must be in a VPC you control and have access to, enabling AWS Glue to send data to it. - -## 2. Create the Access Secret in Secrets Manager - -Create a secret in AWS Secrets Manager that provides AWS Glue with access to the source cluster’s basic authentication credentials: - -1. Navigate to the AWS Secrets Manager Console. -2. Create a generic secret. Name it as you prefer and configure replication/rotation as needed. - -Key fields: - -* `opensearch.net.http.auth.user`: The username for accessing the source cluster. -* `opensearch.net.http.auth.pass`: The password for accessing the source cluster. - -
- -Example Access Secrets - - -![Screenshot](https://github.com/user-attachments/assets/dde7e343-4a9c-4f0b-af6d-e7048ecd1b14) -
- -## 3. Create an AWS Glue IAM Role - -Create an IAM Role that grants AWS Glue the necessary permissions: - -1. Navigate to the IAM Console and create a new IAM Role. -2. Use the following trust policy to allow the AWS Glue service to assume the role: - -```json -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "Service": "glue.amazonaws.com" - }, - "Action": "sts:AssumeRole" - } - ] -} -``` - -3. Attach the following permission sets: - * `AWSGlueServiceRole` - * `AmazonS3ReadOnlyAccess` - -4. Grant the role access to the secret created in step 2 by adding an inline policy like this: - -```json -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": "secretsmanager:GetSecretValue", - "Resource": "arn:aws:secretsmanager:us-east-1:XXXXXXXXXXXX:secret:migration-assistant-source-cluster-creds-YDtnmx" - } - ] -} -``` - -## 4. Create an AWS Glue Connection - -Create a Glue Connection to provide AWS Glue with access to the source cluster: - -1. Navigate to the AWS Glue Console. -2. Create a new connection of the **Amazon OpenSearch Service** type. -3. Fill in your source cluster’s details (including VPC, subnet, and security group) and use the secret created earlier. - -
- -Example Glue Connection Configuration - - -![Screenshot](https://github.com/user-attachments/assets/b5978b2e-de58-4d46-ad47-ac960e729b89) -
- -## 5. (Optional) Examine the Source Dataset - -The sample dataset we'll use is the [AWS Public Blockchain dataset](https://registry.opendata.aws/aws-public-blockchain/) ↗, which is available for free. More information can be found [in this blog post](https://aws.amazon.com/blogs/database/access-bitcoin-and-ethereum-open-datasets-for-cross-chain-analytics/) ↗, and you can browse its contents in S3 [here](https://us-east-2.console.aws.amazon.com/s3/buckets/aws-public-blockchain) ↗. - -The Bitcoin transaction data we'll load into our source cluster is located at the S3 URI: `s3://aws-public-blockchain/v1.0/btc/transactions/`. - -## 6. Create the AWS Glue Job - -Now, create a Glue Job in the AWS Glue Console using the connection you created earlier. - -### S3 Source - -1. Set the S3 URI to `s3://aws-public-blockchain/v1.0/btc/transactions`. -2. Enable recursive reading of the bucket's contents. -3. The data format is Parquet. - -
- -Example Glue Connection - - -![Screenshot](https://github.com/user-attachments/assets/6fc4c0da-45b9-4c09-ba73-1619f59c9dd3) -
- -### OpenSearch Target - -1. Select the AWS Glue Connection you created. -2. Specify the index name where the Bitcoin transaction data will be stored. - -
- -Example Data Sink - - -![Screenshot](https://github.com/user-attachments/assets/264d0d17-f7f4-4c07-8567-6cae47c3ccd1) -
- -### Pre-Configure the Index Settings - -This is an optional step. By default, the Glue Job creates a single-shard index. Since the dataset is approximately 1 TB in size, it's recommended to pre-create the index with multiple shards. Follow this example to create an index with 40 shards: - -```bash -curl -u : -X PUT "http://:9200/bitcoin-data" -H 'Content-Type: application/json' -d' -{ - "settings": { - "number_of_shards": 40, - "number_of_replicas": 1 - } -} -' -``` - -You can also adjust any additional index settings at this time. - -## 7. Run the Glue Job - -Once the Glue source and target are configured, run the job in the AWS Console by clicking the **Run** button. You can monitor the job’s progress under the **Runs** tab in the console. \ No newline at end of file diff --git a/_migrations/other-helpful-pages/migration-timelines.md b/_migrations/other-helpful-pages/migration-timelines.md deleted file mode 100644 index 51d4302904..0000000000 --- a/_migrations/other-helpful-pages/migration-timelines.md +++ /dev/null @@ -1,105 +0,0 @@ -There is no *one-size-fits-most* migration strategy, this guide seeks to describe possible sample scenario(s) with the goal of helping customers plan their own migration strategy and estimate costs accordingly. - -## 15 Day Historical and Live Migration - -Key phases: - -1. Setup, Planning, and Verification (Days 1-5) -1. Historical backfill, Catchup, and Validation (Days 6-10) -1. Final Validation, Traffic Switchover, and Teardown (Days 11-15) - -### Timeline - -```mermaid -%%{ - init: { - "gantt": { - "fontSize": 20, - "barHeight": 40, - "sectionFontSize": 24, - "leftPadding": 175 - } - } -}%% -gantt - dateFormat D HH - axisFormat Day %d - todayMarker off - tickInterval 1day - - section Steps - Setup and Verification : prep, 1 00, 5d - Clear Test Environment : milestone, clear, after prep, 0d - Traffic Capture : traffic_capture, after clear, 6d - Snapshot : snapshot, after clear, 1d - Scale Up Target Cluster for Backfill : backfill_scale, 6 22, 2h - Metadata Migration : metadata, after snapshot, 1h - Reindex from Snapshot : rfs, after metadata, 71h - Scale Down Target Cluster for Replay : replay_scale, after rfs, 2h - Traffic Replay: replay, after replay_scale, 46h - Traffic Switchover : milestone, switchover, after replay, 0d - Validation : validation, after snapshot, 7d - Scale Down Target Cluster : 11 00, 2h - Teardown : teardown, 14 00, 2d -``` - -#### Explanation of Scaling Operations - -This section assumes a customer chooses to deliberatly scale their target cluster for backfill and/or replay to enable a faster and/or cheaper overall migration. In the absence of this, backfill and replay steps may take much longer (likely increasing overall cost). - -This plan assumes we can replay 6 days of captured data in under 2 days in order for the source and target clusters to be in sync. Take an example of a source cluster operating at avg. 90% CPU utilization to handle reads/writes from application code, it's improbable that a target cluster with the same scale and configuration will be able to support a request throughput of at least 3x in order to catchup in the given time. The same holds for backfill for write-heavy clusters or clusters where data has accumulated for a long time period, to follow this plan, the target cluster should be scaled such that it can ingest/index all the source data in under 3 days. - - -1. **Scale Up Target Cluster for Backfill**: Occurs after metadata migration and before reindexing. The target cluster is scaled up to handle the resource-intensive reindexing process faster. - - -2. **Scale Down Target Cluster for Replay**: Once the reindexing is complete, the target cluster is scaled down to a more appropriate size for the traffic replay phase. While still provisioned higher than normal production workloads, given replayer has a >1 speedup factor. - -3. **Scale Down Target Cluster**: After the validation phase, the target cluster is scaled down to its final operational size. This step ensures that the cluster is rightsized for normal production workloads, balancing performance needs with cost-efficiency. - -### Component Durations - -This component duration breakdown is useful for identifying the cost of resources deployed during the migration process. It provides a clear overview of how long each component is active or retained, which directly impacts resource utilization and associated costs. - -Note: Duration excludes weekends. If actual timeline extends over weekends, duration (and potentially costs) will increase. - -```mermaid -%%{ - init: { - "gantt": { - "fontSize": 20, - "barHeight": 40, - "sectionFontSize": 24, - "leftPadding": 175 - } - } -}%% -gantt - dateFormat D HH - axisFormat Day %d - todayMarker off - tickInterval 1day - - section Services - Core Services Runtime (15d) : active, 1 00, 15d - Capture Proxy Runtime (6d) : active, capture_active, 6 00, 6d - Capture Data Retention (4d) : after capture_active, 4d - Snapshot Runtime (1d) : active, snapshot_active, 6 00, 1d - Snapshot Retention (9d) : after snapshot_active, 9d - Reindex from Snapshot Runtime (3d) : active, historic_active, 7 01, 71h - Replayer Runtime (2d) : active, replayer_active, after historic_active, 2d - Replayer Data Retention (4d) : after replayer_active, 4d - Target Proxy Runtime (4d) : active, after replayer_active, 4d -``` - -| Component | Duration | -|-----------------------------------|----------| -| Core Services Runtime | 15d | -| Capture Proxy Runtime | 6d | -| Capture Data Retention | 4d | -| Snapshot Runtime | 1d | -| Snapshot Retention | 9d | -| Reindex from Snapshot Runtime | 3d | -| Replayer Runtime | 2d | -| Replayer Data Retention | 4d | -| Target Proxy Runtime | 4d | diff --git a/_migrations/other-helpful-pages/provisioning-source-cluster-for-testing.md b/_migrations/other-helpful-pages/provisioning-source-cluster-for-testing.md deleted file mode 100644 index 69b4e930e6..0000000000 --- a/_migrations/other-helpful-pages/provisioning-source-cluster-for-testing.md +++ /dev/null @@ -1,91 +0,0 @@ - -This guide walks you through the steps to provision an Elasticsearch cluster on EC2 using AWS CDK. The CDK that provisions this cluster can be found on the `migration-es` branch of the `opensearch-cluster-cdk` GitHub [forked repository](https://github.com/lewijacn/opensearch-cluster-cdk/tree/migration-es). - -TODO ^ lewijacn seems like it should be updated? - -## 1. Clone the Repository for Source Cluster CDK - -```bash -git clone https://github.com/lewijacn/opensearch-cluster-cdk.git -cd opensearch-cluster-cdk -git checkout migration-es -``` - -## 2. Install NPM Dependencies - -```bash -npm install -``` - -## 3. Configure AWS Credentials - -Configure the desired [AWS credentials](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html#getting_started_prerequisites) ↗ for the environment, as these will dictate the region and account used for deployment. - -## 4. Configure Cluster Options - -The configuration below sets up a single-node Elasticsearch 7.10.2 cluster on EC2 and a VPC to host the cluster. Alternatively, you can specify an existing VPC by providing the `vpcId` parameter. The setup includes an internal load balancer, which should be used when interacting with the cluster. - -Copy and paste the following configuration into a `cdk.context.json` file at the root of the repository. Replace the `` placeholders with the desired deployment stage, e.g., `dev`. - -```json -{ - "source-single-node-ec2": { - "suffix": "ec2-source-", - "networkStackSuffix": "ec2-source-", - "distVersion": "7.10.2", - "cidr": "12.0.0.0/16", - "distributionUrl": "https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-oss-7.10.2-linux-x86_64.tar.gz", - "captureProxyEnabled": false, - "securityDisabled": true, - "minDistribution": false, - "cpuArch": "x64", - "isInternal": true, - "singleNodeCluster": true, - "networkAvailabilityZones": 2, - "dataNodeCount": 1, - "managerNodeCount": 0, - "serverAccessType": "ipv4", - "restrictServerAccessTo": "0.0.0.0/0" - } -} -``` - -> **Note:** You can specify other versions of Elasticsearch or OpenSearch by modifying the `distributionUrl` parameter. - -## 5. Bootstrap CDK in the Region (If Needed) - -If this is the first time you're deploying CDK in the region, you'll need to run the following command. **Note:** This only needs to be done once per region. - -```bash -cdk bootstrap --c contextId=source-single-node-ec2 --c contextFile=cdk.context.json -``` - -## 6. Deploy CloudFormation Stacks with CDK - -Deploy the infrastructure using the following command: - -```bash -cdk deploy "*" --c contextId=source-single-node-ec2 --c contextFile=cdk.context.json -``` - -Once the deployment is complete, the CDK will output the internal load balancer endpoint, which can be used within the VPC to interact with the Elasticsearch cluster: - -```bash -# Stack output -opensearch-infra-stack-ec2-source-dev.loadbalancerurl = opense-clust-owiejfo2345-sdfljsd.elb.us-east-1.amazonaws.com - -# Example curl command within the VPC -curl http://opense-clust-owiejfo2345-sdfljsd.elb.us-east-1.amazonaws.com:9200 -``` - -## 7. Clean Up Resources - -When you are done using the provisioned source cluster, you can delete the resources by running the following command: - -```bash -cdk destroy "*" --c contextId=source-single-node-ec2 --c contextFile=cdk.context.json -``` - -For a full list of options, refer to the CDK options in the [repository documentation](https://github.com/lewijacn/opensearch-cluster-cdk/tree/migration-es?tab=readme-ov-file#required-context-parameters). - -^ TODO: Are we advertising a fork? Seems like this should be fixed up \ No newline at end of file diff --git a/_upgrade-to/index.md b/_upgrade-to/index.md index 0eea3d6209..696be88c21 100644 --- a/_upgrade-to/index.md +++ b/_upgrade-to/index.md @@ -1,6 +1,6 @@ --- layout: default -title: About the migration process +title: Upgrading OpenSearch nav_order: 1 nav_exclude: true permalink: /upgrade-to/ @@ -8,15 +8,14 @@ redirect_from: - /upgrade-to/index/ --- -# About the migration process +# Upgrading OpenSearch -The process of migrating from Elasticsearch OSS to OpenSearch varies depending on your current version of Elasticsearch OSS, installation type, tolerance for downtime, and cost-sensitivity. Rather than concrete steps to cover every situation, we have general guidance for the process. +The process of upgrading your OpenSearch version varies depending on your current version of OpenSearch, installation type, tolerance for downtime, and cost-sensitivity. For migrating to OpenSearch, we provide a [Migration Assistant]({{site.url}}{{site.baseurl}}/migration-assistant/). -Three approaches exist: +Two upgrade approaches exists: -- Use a snapshot to [migrate your Elasticsearch OSS data]({{site.url}}{{site.baseurl}}/upgrade-to/snapshot-migrate/) to a new OpenSearch cluster. This method may incur downtime. -- Perform a [restart upgrade or a rolling upgrade]({{site.url}}{{site.baseurl}}/upgrade-to/upgrade-to/) on your existing nodes. A restart upgrade involves upgrading the entire cluster and restarting it, whereas a rolling upgrade requires upgrading and restarting nodes in the cluster one by one. -- Replace existing Elasticsearch OSS nodes with new OpenSearch nodes. Node replacement is most popular when upgrading [Docker clusters]({{site.url}}{{site.baseurl}}/upgrade-to/docker-upgrade-to/). +- Perform a [restart upgrade or a rolling upgrade]({{site.url}}{{site.baseurl}}/upgrade-to/snapshot-migrate/) on your existing nodes. A restart upgrade involves upgrading the entire cluster and restarting it, whereas a rolling upgrade requires upgrading and restarting nodes in the cluster one by one. +- Replace existing OpenSearch nodes with new OpenSearch nodes. Node replacement is most popular when upgrading [Docker clusters]({{site.url}}{{site.baseurl}}/upgrade-to/docker-upgrade-to/). Regardless of your approach, to safeguard against data loss, we recommend that you take a [snapshot]({{site.url}}{{site.baseurl}}/opensearch/snapshots/snapshot-restore) of all indexes prior to any migration. diff --git a/_upgrade-to/upgrade-to.md b/_upgrade-to/upgrade-to.md index 340055b214..00950687a5 100644 --- a/_upgrade-to/upgrade-to.md +++ b/_upgrade-to/upgrade-to.md @@ -6,6 +6,10 @@ nav_order: 15 # Migrating from Elasticsearch OSS to OpenSearch + +OpenSearch provides a [Migration Assistant]({{site.url}}{{site.baseurl}}/migration-assistant/) to assist you in migrating from other search solutions. +{: .warning} + If you want to migrate from an existing Elasticsearch OSS cluster to OpenSearch and find the [snapshot approach]({{site.url}}{{site.baseurl}}/upgrade-to/snapshot-migrate/) unappealing, you can migrate your existing nodes from Elasticsearch OSS to OpenSearch. If your existing cluster runs an older version of Elasticsearch OSS, the first step is to upgrade to version 6.x or 7.x. diff --git a/images/migrations/migrations-architecture-overview.png b/images/migrations/migrations-architecture-overview.png new file mode 100644 index 0000000000000000000000000000000000000000..3002da3a871c241f41e1d19e5967371382a02714 GIT binary patch literal 360344 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

AXVBUUdaq7sG}3Jj{%BjZ^n% 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

XQVIwPj29{*h)un>Y0(lX@9+4T7W!<~o=(X9e?1 z=I(k@{jn+CG3}kp-7*#xwDue|Y7Esu?-nwrq<|{Uctui{lq|0bNZ*?Mt4GNzU*vsC zq^F9)XFrW#iUTvwPlnxIL6`{r({zc&`&guxFa+T=2|2kX%R$ffn#h<8JZ60A7wxa* zE+>_jUrwYptn@?XH7lH8isss^G}F5zl_W8L69pl%%Em@amL;m9URqiaBx{ZffSWHV7Oc$7C zm&GouQ92e+I;jlp+aU840qh<7+$lyjDu?EDph;_nmaX%$KYBeqz|WW0fvvQejU>;p8}-3#+;7 zV~83ac)b<-5lQAIgCPQob6EqeYNXs;{Rg{m!48t5U{vN*wM+$j)YTU2eT#F&$d`xE z)G=fPCxrLBmnNl$v`skN_qT`m4a$SGYe;M|iILp<O@0!=wseJ~C2_w%2|5hU1|6l+rWH4> z><#fM}f)&;AP?>jQSObvuhu>=s$d6_|mbMezC{;miw>Z{B+s>QGGz6m5uM z>*wD4iO|F~iTz_EbXGS3u*!xK$I-@^;C*Mv^)ecdD>SgUBt@oPt%pQrEDYq1wv2-kf4**0uaK;BR_CoVL|Um`n#PRS#~Jpqqhw0tH4q&CPBhFA#Uc`hv?3)G*H^G4LM30wQ15Q&oT zYG!buA?q2!*-EeO58&Sa8AlBB;gG=7ip%BROl*LDI^eRvOdFK8dJ7SpT`rYh-#OrZ zpa_|ReehkvD#&iDe|r1EKaasB2X7omiik!4x&@y>pvsEO+`{O=XFX@5$Ao9&&4Aiw zc*VE;W8F`0VQQ@WgDxIXyT?qPjlBO4WSQ59{B$0E z1)0XZo6rNVA(Z$;DN3k;MrcU(&S&s@(ChKA?qc` zBVdk@wZ!C$2>A@Dc64zU>$?)Rj11a{1+R039rm|g+0A{ju)Z&QD6M>WmFwdE4&wn5R!^H~7uS~pQ zhL|S|P_SAF1iG zvV?R6g$9-1`=U_C;VkW{pB3FsY`MkDCu;&Wv|553Y7Q}FqT~Wl{uf*EMV*woK~V+=Xs~& z8LOFh-^bfcdtcRWx0ucg)z41%bS?g&lku~^swNV^U@grV?bT+{)!M&$P`;*9>{nvu z$~;Z?!YBjLZ6)t;boa%tSdb%V)s3n3D--^e<4xeGQ_pJx~O(`F84{6SIE!4dNJ}XWJA*+^j`qr2pKegN1wfrx@k@%0PerDpZU_e z@I$vkWG6pu^;OYH+qO7pP{Ia1!rjJCUlD6Of0O5BQ0aQW*6SXK)(sGS=OS@Rbxn-~IxQ##YbBcBd!)0s8$OFXY^7ysna zaQm?bKVAM6VGxKf8i6XBvvo%TBDT}8G3J}c-6ZLXS)G1azIDeQI%s1GEwhQHm`2DE zyc2+MQOkqKK#}F@X%HE6lJ^2!Wf?x9JWhvzLm~P>m>ZF2f5`2>+V0w%UUE=tlH7Nx zwKFi(cezJnG`9GU3ya@34js4wFr!Mrmi1&rB|Gs|#&ck!t`bMazhMj-z6Sz3}J zuxh4X{LPB&H*u(nOfafn>p1y8!@|0J2Z#H)=H_iV*$hm>Uhi-2>MC9ur8;Y$qeo_Y zHeiETFt%jOA||`F*UlyKuj!d}piO?wY6SC73gviPhE~ zc3JO?yPDRH@>1{3I$@lj8nYoljxy#A%zY~x=qwQ_T>d=#BGf0NJ(uQws0>$X(~R`~q7zftdRg$2lt^dX9Q8p&oybH0kjP3h_huaQ=fgh&3>*yQJ183(Cn1+89Fn2B~)Lj_C5qHmfeW*2L*%)ZmeEB~A| zAdaxTp4-%Y)ioNk)CN&Zu8tLrhGsayA+ z>#Pl#uTk2j<>(t`o!7AHdh?-4Xt8L_TXc{2be+VTxDEJhCCMTG^6cp06HmnXPsGR< zJ-o=4KO$K?5$x1tbV-fXzNZuC2I@%>G&vSgOynm7Q<`s~nIqDT1fgR$v;_ z!R(xz0Lcz_?nbvO{GUudBNhUPe{E zOKnfxkgbO4?ji%<={mJ?Hrv#D4I+aE+rZyqyPm9p2>(+&taPBt<`k(Cor^OgQH^_n zAUvPZQ81Ewb{-U<`5e|hr%}J_J;O=rR)+$6Lnwyro^5SyEr;NDU3=Yvm*zC6#%W9P0i=Jp z?gN$|3#BMRG{TvhxRpY*m6Yz+muhxGPy6Ne9ZcS*U~XGv5R~jr|Cmk5C~k4(d(7@w z_^QTr`(oLTOEG&x6jtZ44h}NEB?fSr9+elEJUfHk9nZR|ZO?lLL+lu4VUEC72m9Bk zG9f} zLS}_|Lm{FtyV#I7#h_5zCGrVCD6M-Y?N3M;$BadWPuKmJ<@3T?*-{(4%bDU@2iP1w>_M@UbX;XjC-a2 z6bxkqrJS!}HW02UGO(Du$TO$YImiupe@?YDgaFJec0M{F1dB2=~W6~Gqu9uKz0Z6Yhy)jvYc9}#fyU$eP14{RM|v%EKgLF zzj*3k{Lg*ppM!~B7WMHO7n(Jy?mV^6HF_lP?P;8!5+ea-Qp5F2rwL>l0n4XG5aKCWi{#F`KD-Y8(`29gAFbmSC9GrM!U` zPj5d|Vc8x_MLOeU0^?~cV!UUQF5PcIVF@BX*DC+KzHKLd*X-yk-P_D<(C26Ys8!bq zb#>(eBy7`pO@n^e3-JuzP(8^$6WC&J?3eAt+D25I{b>ITgJa=@s-Z;y)1Z9Fg@;B5 z3fES~XgQaE?kEQerswb)e;4aL4C7_HU{Ef)?RIf>8AXH5|9@|tyW}dE7LC( zXoB`!Rxi9b-L(t}5%vxkQR;3aci?K9b4BJ*dO=ehzdh*7QV8uUPsgAPNj(o;QRfs` z&xf#ToZjNgi_InJcE6-kT@jzwc#mFj)`XdydQy1YjqU}{6ENgk=$U)>tr@H1>=ev- zP_?Ygc$Hr^*0)t=jYV9R7?nb1DQeRE^Z*e5;yWA0jVjay7@gucOxOf!p_A1w%Kb~UWO=eHSh53O_ep~Eggnv}zG6zU4B)_S9q5N~5^FpgB^QXC9ox2Wa+eue_k?>k# z6U_H5Qz%&4{Fz;nK*>h9r8)QhANxiuLr1eNmu}r5?zQ`=W3h+(@T&?)E?%jTV?h%b z$3Jn0PA_^2Q^jSdFLtw(7-}-)@``a`0SZSus>yCVDuZq^etJBI%dKilhytF_8FP)m zOp$_xui)DLJhSfF-Hzr}co6R@8B7Vc{D!>kdt&R^{xaYPlT&fcdc1i%)Hxh-k~vGS zo(&L*t%@U{1(1N1Cz#F;LD0#YXEalB&X?{egK5%i(J=lvv&q=j)s*_XQ#nmfUnelF zm_?a?evl~=mQM3sK^04XDdLPs5?F4=$yj(GMWaPhZE2nSusB!u=MIH=#rc-z;-*@W zRM=A&Oa!Eb;W`7Z3TU4)XQ4J4`EVIaIbpwrL>LPosPIgS)D@I%D+p)*ojnQLhlL@r zfiV2_Z4&qG;?D;cGkv05n{LgqxAP`0nTmC@*gTs5>gdBDKXL11L#>|{1@(zivTp3# zAGcArrc@{|bZds&VPR%AFPiF1Gtvu_2iRaR1>;y8G@*bk@?k2NI=^tf`;MvNjQb@* z3l)+OMz3Oe=c z*QX3gZsVwu=nk%!WJgDtITp>7O%Hr4hnuAfo&=BfPQIh{5i@bAQ_-TzKk@X4Fw%X| z<@Lmj0ASn|Ad@8ccqc<5WCA%|_U_#qVQ(e8ZX!S-?y+~_2hZtdR0PRSKNoXo%bPFh zT%CQ>mD}ntROjWw_ZWX@|Y%^NP3?VOJM|9!Rm2w&-99(q?gf@?Dp9@Z4EJN z%l&i#=EphV&H@`$q*vFir*Fi2%s0t6RL$ki9Ns&52Uedv57S2Jy2OLQMFaWIG&JZx zBm?^~&j})3P2M+QI-TA8s9Op98}@*+J7vd9(-$soHw74!1VAf^!e|ic|037~93ZwP z4P7m(QS-RSXS=Z9e-9*s-l7sGXpw2Y;NQ8_F;`Zn#%bEn^xM#27d`9N?phufD$k}5 z>e2nj9ns?Nt}Kt-6@wX2>;h~#&)~H0&q_;4O}IE7Jxzlm^AG^&Wt-=GmCRBw*Q$(y z@7bwlZB_G=(~%|1yg~)TS*9b^;A+_A54=DIg2sq-P?A$gZgB00V1+ioP|5X0L8 z`mIwQt8fZKQNb|`6pu>ydzKC)7B8oSD=UH z6N#|2vCvQ1Ce%T0Lz-SR+u{V`wIQtpns_J(AvjQ8d*3Wnp7c2c=3&XL{-;bXFq*{$ zDSNnga-EwB5jW*bevDcd;J>+P5Bae-JidUt!9(@;wWayJhR${-=cVBt4%L}n7@V1J z%(6jSafBi&noEk>Do0?F)op1X}K-Q4l8$xTMjSYt|{w* zx$o34+26?`cj&E=Vk9lZlT5eH#zs9|?_Jqw`a}K4y>Zp5zuP*_pZ&Six#ok6m%&4y zUyQ>{GljS`b3m; zj;*|BF_?GV%{GsvrfZeU(Y}qJWD2jNp8?SS_+XqO9Uc|g5}V)&q|8S4F_^u1rpvQv zvG|*|42lqy;G2d5a@TY@x%1$4_-7uzMbXqfm(W) z{d%^{;U!A9m3pK?y6tmesbtB**VqhpHvIMb%q|{ak0B@UuiCEfsLu~k*dIUpA0`hJ zMjM?2)0g~Bho`MT(D=_Aw1KqVcxBXwbV(Cv3AHeT9Sny@b}*I_oIviPj(3?YvoJ;D z*60^^5b9@SNQVg494vJR*`<|PY>Zes$Sabq6ZbH=>`cRH3%G-T`VO=3@kU&=RL@?w9O4|C=(D{>){*99V&ZhIJ11` zgbiE`;Ft%49rOF*<|a?dP8?fq!SJoy0kkexTG#o0qtg$&ZfX7&2w7ZCAD9gMYkXIy zl2|{3t;pm9M&HX9CDKp4(RRQW;t6ww?W7ufpC1&aBU28M>b~hI*=lFt=}1|>~s<^+RN_F>~^HiI})6jRhU_nr}R>?o{- z3~ge&OHM1q3Y&{(bLMycMYH$j$Mr?|!L!!TcsRL$xOJa7q*_8Cq2XkD`NMvDSac0&8U|h$^OPx+^M?5G}?Ri z$2eFMC0#w4gqaq|Z8QeAwqBQih38ZPW`HbAb*)h;qW!fraWVLwhQxezGO`?mgt0PJ z@DCTbT?mD^slmRxV0jg%M(sG*34Wj+#>ZAc1q)MR~%>3N?)e39xgopW7c)xX>OjC%V8g zwvkqwj!YnEVT+)>P1D}3`>-~E%yj)Piy*eNfOD;x>SDwx5N$%v>j)UjR7nF@r#aRN zr>Zrzd|iqC%n*q`0SjKVX=TA4097Qut2Lcq1_0$y&>>zcW4-ij7fe-Zd;z0vV^-{% zS7Td~uEC%ifF@zu0t{gQfrN%IOp7{I>~-@8xs|ZS#E|(BViz0^(A-KoeGH&L1ZE{F z+`4EweH#?1pNIs5&AM|QCXIzYQ&&p)&yOFj&oRAFRo)-Bk{4N;CSs8l~e@3 zLWJ&RlJVm4>ljC{v5JxmRdJr!`aAm@-_+&_9|7M44eAfbX);c6mZ>|mj<{`*bsB)X zMVLQ^a+=S4+V1KDiHOs+$&wlN8&ee=;sZ=?EuHhjq=5BhY`S%6J(k0x%$5IIlkw_X zkbULKv~rBm;9F<9PjqKRu}eCbe78wSD?`ak`Ng`>op{b$7uA41P{}^^A&VqFMyfwx{zT$W;c9J)9ccE&qFuIA4IuYH+F|R|H@sN$9Eex@3Q{ ze;n`;2q5M~nJ^)Uno@JfN(q7U`aCU7I28;Zi{4N7v<8Br=&W7y*jUG}Qszniam%0%J2U+q0i6}JI z0j@@SR&iP9;tjhj%(ZLR2wt5lU&Mhi>5g_X78APS)tL-p7Yfn#TLiy;pz{+OsV-7Z z8@T`?8Pd=Lgis$p2I{A(EunrYZV#mi#9+D}oYtlgEt=N7>C*(J3S0Vk(n)UtFEA8# zLKxDyeD1=I^=+r3;AL9uN9CWL0JP1mX{6*rOioyL!g#29eh{qH`2((=q^Og-T6!qe z2|TQTw^#a)r1l_$NI`8zv_Q5QVE&li5+9nhB#470E7qKOSy0PTrB8hy|}hAM~mS99mK%)wih((+JdwL z=2kiL(0xh-J71N`*6{ba5J^z0gAuUx)sLH7u%r~=x+d<<4`;`!ad_~wfy(Lv157y& zJVa%e=E&omw&`y#!kl)cN8;AH&FyF5&Ysf)FPb*m4Df1TlXG&3GN_}r6l1}cgepoR zj>yH8U?J5+oBLa^7b&GeB~G^%lTS0=9Ic*&q+tLu0FmHALmIxsC@6sFlCk)n2_?$- zItz$dQ$gCNCP7z)8!*WI2?JtquE?jW?toSq z7)mr{=)EUr<3XUfjcUJIv|-|ChV}P`odzr6im`k7fv|m`M5vj*om!V{0KP;GIO+j; zb_R7c?CG*?+@(!06*q^Y_C0L{fj-0J#F2uA6|7qYpd&Ki2~XRX{iL5k3`2t{aaaJ6 zT8}a~VWlpSNt%$bGCFZooztN~L#Wp2a4XOu_06xUq~RzNzRWhTZ{s`EB?*M?(KHL) zZI-}4Y(2?(>kq=O>QRiN_{qRuSky=(@ORcmXDGb?=q^~5(3r!@468ah(-7wh5H^iP zjS|V-S%7WA{+_R15E2s_(~4ciy?sN{j|9KfY#fOQjc+Y1IrDhJmy%JQ?PEW6yS#aQpyF${&0i%*>maI zx?cn9axK6Ul^ORq=y!*qPRH+de=^zEF{)@)D+`)Cm=Lt;89U`om6*6xzzy^%)Bi!z zAIvo%v@Slixwi$|hbXwYwd2p1bbkoJ2146Ow`x9aqKNY-KtvdF)O!l}7>2E!--H`a zDAgQ@_@MNUs_}K4FQVWHp>Cs=4FKGf*DAOP>6O6Q8fIG4H!-GwrNOeq-*x2#IO-m7 zYBqHXsJzeTHnRw2eRr>!kCHe~L%=4ASsQMsbN5EhsZ|6pMoRBU%T%O0`NrAk5vZ z5>TZM){L&fro0(Bj~-4uPd86lbM99_2ERZN&craf;ne`kR+0reyif6M)nVC(q515* zn;^MSX0+Z5^KcV9q$wT*8la8&bmh@m4HOHt6cxN}NFsl-4}&`ZSa~vUx2YT*=ovc3 zfu6r?E{*z4+y1B6&fOXfB=jeU>(ugk!l0|+H_l0He!C5+z*}~| zg17N6o=G3)iUbmRD^#GkS&X1LbqW}>17Z5Ge73$eu|AFn`ox!*w*~tEmU0O>D$v7+ zL@IBJ=_aiB)^`VC{?u$vTPEwYv7_u%Km4s?NscwV^$sV=DSs(Z1&bk zYQj%Nirt+b$*)8m*Qe=^F0g|@qvA@sjH8*F)M(zDB~wx3ef%?v<($Q=5QI8w>b+VY z*)f7M#Q%I^y6aDaA|`v;1cF4m;W;rwGX&y70N1ZpEOI+vc!MLAY9i zH>8FXyPD2Mro>l56nxA-E;L<8cJh}fEe$#^c zK+4RZed@z?KL-RiN~y)2&(ZwnAVPG2GeW1Vt>SCXh5IoC$j{6ON!?5R`Tj0z=iuR9 z`o>d5;d&XxKIxQIjhaQVmirvvmB1=SG}`1gfu{zp#q&I)rRu*}LpsP$aB@&uSnWPc z)%TjO3-xKRTzGsbniXyg9fli|6>Wt^Emtq`>k;|svg&T^OUQ-o}o(uD* zDiC@9a!9EVikywd)SFi3%pJdJNO8_KiB^#}0M|}N_O)kwBM#GZA_6RiymZ@j-&4zH_qGl4fMhmgwt9Wr>b-wCGZ20_qU zdXM{0Y?meHlHuuDYd)e#{Yp|7^%X8}rps-yNfD=wi!Fw#%J=7aNd}POHvc-k;RYCB zdqRC>zC^XwNh}M$F?lg{ijnxX2J=N1ZLLV5e&n}dv59vH!j%9*bu4DuV1QO``qX!rpj{5W?;}5sA)+;E zl~xKddO8SK!Avm~R@Cg9Y*><>g79uJ*<0!SRCxG!+YW~R$~T)u+@EE^ObdY~nLy|V zc=BaCDI54Y^@iqYn}qQu-Ld`(E`C5aw4@wmp1Dza9W>YW6PJ`X39${_{P_?MokcL` zLT^WEH9a1Udyr(R`t$9dv;RgOd=(~#Mb3wiVJ4RyI2u3LO)77~rKI$+LtOzCaPM*1 zJk*tw(bkaaBLA$VeqghI_UC|q7MVs9;?RV?derPaOF6h9Qr6E!FwQ0g*SYJ+9!Mxy zK%Di9sQmZ=^Y6Bf;R=Os?b&FG{{YsH6M8ozu^dhs^i$N zKu2#-LXkySbMgx+DnfC&ygQsw<5K=oVP(L*xRF6)%sj<=xCExAX?k#G(5t7x2OG_o zJ^u!l#{0)3O9~mr+-4eg1W7dQW`y}IwRWJeqPy#&>>Y|1`Zz@GUVUKhg8bCTD_LVF zs2`p2u<#)a&YYhpa%dh5SX+o$WA%Z;tq<*}28t8an7S8>?IJU&mR}l9-%=~;yHHiu z{ZI}Dbjux^IVg+jBS2=)!B`l{Q(TlzZSFn66CAO0A4Cd<>1zyRCVDv6q`ZC!n)A2V z!MkXnTGnp2th-a)WPG&=gg&}%a);b3aD!w)5L`)lINEXMSp8D74d;^Ry3PCFP!je9 zRrVVsmyY?&6*zsBXr3R8bs)nF;tyHX;>6&lxXjN2DK2N1 ziYn`E$=VInuhsHWPM|8eF1SaA8f7{2AS0Pio5A}HM%7x3wOGTmVG2<7FrdeRL5Lg24Y6e@_VDdff_r0c`4-+$Rn%RA8%Qb8M zP~Dtap;~9qFAJBD%RN-1s^8;6h2Yyz;LHI zoSBS4me_z|c>G+3|6!)rbq0+^lb;2Y=b_J8?!J2n6tDp`Omsj<%6Pc4hio*Yh9_$~sdf)SEDP zq#ECn0Np9tA^{4Q<$A-E%T7Y-FZLc>oiGJC$B44HrNz1|o*kyB%4WP;MzciQetx`5 z#hm2|nlg8bfsZ-x;Uk&+V>364wUfpTFFw9dmQ}SV>F_S@Ud;@3z8B;sXjx2MbTZoa z9y+hyZ0e6P0cPeW>&xPIXuvf!^vuG>jQ43^m>W<8waHIZ9Rzw2H~oa$Id#T=p*qRJL^xg9aX%3V)>1-_T0xmH9F;sPF-j%HrwhA}q3Vs*utDMlum+orx!ZEh|(Hlq6Op?&U;1F{e_uCr_I zIor9YFEtQ7FW zk~{?0Yd2q7OZIqPH)8NDk|$#bsWZ`&{qGvHDv&=NVAs87+_X#PKo}PmNtB72W7AVz z8>2@sTuCHQhKaLWo?kFs+w0L=7Jv10$vSC1W_~0w)IKj~G~F>=pcmhK%cu;h=5d$Lcc^7`;WsW3`{uE$xCdo!)h=Zw zSgllJ)GJik?P2)#4*cFIH;aR*X4A`%YfrF*fZBv_6iZ8inF3_u^IL|fzqsVE%Bk5y z{hGZ>E6YWc-^dG2EP|1luZI#KczIQLjM{*4ChhQDx{Ng_evGd@vE0V$Fo2?&GSbRi zDy^<&{$qIRzg%GAq{Kvi0t18(&$nG|h^!VnuGVkLAT6z|U}~@8{R65AWu&+5-Wz3c zKh{;IL^mn%t>*$GThUnYl5{R8ppe8P0k#QIZkWM?d7PjWRaW@z48Bf5r9sQCy_!*O zEjQSRy2of?97E*!^or4O<&`Jsj{_8Q0zUTYD@4c?~I-Vml zJkQ>Ht$nY1-D|DyAbFVS2kVoTdU@8>52@K`0+cjFV8IAF435>DkSEakD2WTrHk@$i z`T|MyN-n9(a*$VVY|H6`Z&+n>)GL1v>0v>;2PZD}x`JzG6gB~cEPfhWQs)~jvtc9+ z#ttuRq!_?|$YW2aR6QK}wZY{5=qK4%(h|?h*4Wo|Xqz_O7Pb61=&|QAK4yk^cYJ`y zl6N7{8I!^-*|4$6ZGVZ4j9I7;e6V6e=qw^#M{gxJ&G;Z{Ah=i@ixA0erc$CNi0#@+;SR(_`pbR$Ks^?$930#sE%>VRdtcC&A@qR(y<&;bX`Lt9 zBYo~3Wt$+l42Q|SUqKgfbm7F{>YEd9XTQF&k-bpy&@WBreO~oNwaF35h^lqCP)92x zq4zF>@B-1`(gp%8?nz_c!a%dREB5v?-cR`-cKRqs;2d-Abj}DPvXEJ+gL0lijmhhgC-MPvA{W$}m9ey>Wqy%%oT?b^9o79|RLF-W?bX%w2e9E1{V9@F5+{XaLQtFeW za^RDhE9pbCaU7Zq2d3&rL2p=DUGlnZT6DFebakp9<_|`!4%JbQJfN2S`krkk($QOo z&l|WhoR{>%4#u^*%~t{s(se6{;e45g8iv;y-NCL>1bplKvi5c9yq}nq`|eYVy@WIU zj`Os0YT;L~11 zk=4wbvmH--N30fYLksYOt0(K9;J2u~QnM{814{nn9nqK&O?Dwy+ zY~G9lb;QoCd4bSVIZv-)Vxi&ju9fig{oYLi%bfTkO$Pbtz`nbk^-P>=C+J61xeGP# zZIL7gn+!}Gdpx}}bzgRrx=a%+*1mXV#mrx)A0a&O8zOj|yq|x0gnj8#`;2&g1(>&| z|NC9+B!bet_F(cK>1I?P|En^XDqk}H6Hgg>M5Xw7usg3!*}Z1lePiF4z!_z^C7pae z{C@RIMjzI}fm!RMw(eF%LsAMi$ESH$ufp-t{R%CD&x6nX;G!c3!{)o)n>lZJjC{!7 zbUn1<3^iPHq;G>oFYaUCPHZq2Yuj%sN}ouS-0AJnVyb#D44R?C#yt50uT=^GkLn8L zAQkIhgJv@*Z!a$b|4KW?^j{fIc* z)i{Ol!&saJj+Zt}8MAYYmWDPuLL%-O1qH?Rh|}g#r?>h$!ocmq7)7aOF$Ii(=>!81y~jtJFSNDv!lAi<(X4mz9ZX zfBvHRu(=VG2mz+PYx5bK0HcYf! zJVgQvC0pS&wIzimCFr$#mZZdq_dg7n?;fWRd z$TLXA*f1KZF2@}9k7IX*e;m^c#UwBa8I0GOTx;?;z*cv6Dg` zHVR}BlpOe6PwPX529zrNmOd9vrr(->-{^aYqm&WxA=SdNU@q&l}CN!=QRERX<|$GM&iJh3uqwKMbMcIGRsV^X|q`(BdUY+q?si(4!*b+R<-&3V}~ z)rtOJ=N;8$ze#+INUD@`j#uF=t=}jdiE_tAkm!=%LX;j8*B+EkW_!%|2 z_;W-PA_yI7)X_EEd<3mot4Pe^#%ErW)k84#N@7SdwW@9m0n-82vyKm}&Ulqqe@R%y zMW9hS1GEl6CfJ%AW=MOlM(bC7YEAuk`)L>-GTm|nZc9m%X95%>l>DK4`9sW4<; zs->hVTW;(j(HE58&BC7gzjK4k|3t38w%{tKwswXcF*4Fzd{Tatrdbfa?qP|;T*rPw z-o)VUa5P;{>UyUHce&m{=}Y`7|K0mS$n!WNNe z)R#Rxo4dRQj9bzo>Y6CI@cq|Y6`8T)b1!6=DZSwgt+lJlT1m7xO@fO1q>*rpj$*3r z*KcqMuSJWGUj5XOF(brh�W>ct0ezHZ_?fNbJjcGn(cwz@Krf`iGp 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 Y_(NsEibb}D+Zcer)78&qol`;+0OiUQjsO4v literal 0 HcmV?d00001 From 301929fa23c8ac92a7c8bf01b35f94d5bf47c338 Mon Sep 17 00:00:00 2001 From: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Date: Thu, 5 Dec 2024 08:29:55 -0500 Subject: [PATCH 39/69] Update repo maintainers: Remove maintainers that are not active (#8882) * Remove Melissa Vagi as a repo maintainer Signed-off-by: Fanit Kolchina * Remove Stephen Crawford Signed-off-by: Fanit Kolchina * Add Melissa and Stephen as emeriti Signed-off-by: Fanit Kolchina --------- Signed-off-by: Fanit Kolchina --- .github/CODEOWNERS | 2 +- MAINTAINERS.md | 10 +++++----- README.md | 1 - 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 815687fa17..61a535fe91 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @kolchfa-aws @Naarcha-AWS @vagimeli @AMoo-Miki @natebower @dlvenable @stephen-crawford @epugh +* @kolchfa-aws @Naarcha-AWS @AMoo-Miki @natebower @dlvenable @epugh diff --git a/MAINTAINERS.md b/MAINTAINERS.md index 55b908e027..b06d367e21 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -9,14 +9,14 @@ This document lists the maintainers in this repo. See [opensearch-project/.githu | Fanit Kolchina | [kolchfa-aws](https://github.com/kolchfa-aws) | Amazon | | Nate Archer | [Naarcha-AWS](https://github.com/Naarcha-AWS) | Amazon | | Nathan Bower | [natebower](https://github.com/natebower) | Amazon | -| Melissa Vagi | [vagimeli](https://github.com/vagimeli) | Amazon | | Miki Barahmand | [AMoo-Miki](https://github.com/AMoo-Miki) | Amazon | | David Venable | [dlvenable](https://github.com/dlvenable) | Amazon | -| Stephen Crawford | [stephen-crawford](https://github.com/stephen-crawford) | Amazon | | Eric Pugh | [epugh](https://github.com/epugh) | OpenSource Connections | ## Emeritus -| Maintainer | GitHub ID | Affiliation | -| ---------------- | ----------------------------------------------- | ----------- | -| Heather Halter | [hdhalter](https://github.com/hdhalter) | Amazon | +| Maintainer | GitHub ID | Affiliation | +| ---------------- | ------------------------------------------------------- | ----------- | +| Heather Halter | [hdhalter](https://github.com/hdhalter) | Amazon | +| Melissa Vagi | [vagimeli](https://github.com/vagimeli) | Amazon | +| Stephen Crawford | [stephen-crawford](https://github.com/stephen-crawford) | Amazon | \ No newline at end of file diff --git a/README.md b/README.md index 52321335c7..807e106309 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,6 @@ If you encounter problems or have questions when contributing to the documentati - [kolchfa-aws](https://github.com/kolchfa-aws) - [Naarcha-AWS](https://github.com/Naarcha-AWS) -- [vagimeli](https://github.com/vagimeli) ## Code of conduct From 7e7700ff5847ebbb49feca258afc48d0bcec30df Mon Sep 17 00:00:00 2001 From: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Date: Thu, 5 Dec 2024 14:11:31 -0500 Subject: [PATCH 40/69] Add link to default field documentation (#8890) Signed-off-by: Fanit Kolchina --- _query-dsl/full-text/match-bool-prefix.md | 2 +- _query-dsl/full-text/match-phrase.md | 2 +- _query-dsl/full-text/match.md | 2 +- _query-dsl/full-text/multi-match.md | 4 ++-- _query-dsl/full-text/query-string.md | 2 +- _query-dsl/full-text/simple-query-string.md | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/_query-dsl/full-text/match-bool-prefix.md b/_query-dsl/full-text/match-bool-prefix.md index 3964dc5ee8..6905d49989 100644 --- a/_query-dsl/full-text/match-bool-prefix.md +++ b/_query-dsl/full-text/match-bool-prefix.md @@ -216,7 +216,7 @@ The `` accepts the following parameters. All parameters except `query` ar Parameter | Data type | Description :--- | :--- | :--- `query` | String | The text, number, Boolean value, or date to use for search. Required. -`analyzer` | String | The [analyzer]({{site.url}}{{site.baseurl}}/analyzers/index/) used to tokenize the query string text. Default is the index-time analyzer specified for the `default_field`. If no analyzer is specified for the `default_field`, the `analyzer` is the default analyzer for the index. +`analyzer` | String | The [analyzer]({{site.url}}{{site.baseurl}}/analyzers/index/) used to tokenize the query string text. Default is the index-time analyzer specified for the `default_field`. If no analyzer is specified for the `default_field`, the `analyzer` is the default analyzer for the index. For more information about `index.query.default_field`, see [Dynamic index-level index settings]({{site.url}}{{site.baseurl}}/install-and-configure/configuring-opensearch/index-settings/#dynamic-index-level-index-settings). `fuzziness` | `AUTO`, `0`, or a positive integer | The number of character edits (insert, delete, substitute) that it takes to change one word to another when determining whether a term matched a value. For example, the distance between `wined` and `wind` is 1. The default, `AUTO`, chooses a value based on the length of each term and is a good choice for most use cases. `fuzzy_rewrite` | String | Determines how OpenSearch rewrites the query. Valid values are `constant_score`, `scoring_boolean`, `constant_score_boolean`, `top_terms_N`, `top_terms_boost_N`, and `top_terms_blended_freqs_N`. If the `fuzziness` parameter is not `0`, the query uses a `fuzzy_rewrite` method of `top_terms_blended_freqs_${max_expansions}` by default. Default is `constant_score`. `fuzzy_transpositions` | Boolean | Setting `fuzzy_transpositions` to `true` (default) adds swaps of adjacent characters to the insert, delete, and substitute operations of the `fuzziness` option. For example, the distance between `wind` and `wnid` is 1 if `fuzzy_transpositions` is true (swap "n" and "i") and 2 if it is false (delete "n", insert "n"). If `fuzzy_transpositions` is false, `rewind` and `wnid` have the same distance (2) from `wind`, despite the more human-centric opinion that `wnid` is an obvious typo. The default is a good choice for most use cases. diff --git a/_query-dsl/full-text/match-phrase.md b/_query-dsl/full-text/match-phrase.md index 747c4814d9..3f36465790 100644 --- a/_query-dsl/full-text/match-phrase.md +++ b/_query-dsl/full-text/match-phrase.md @@ -268,6 +268,6 @@ The `` accepts the following parameters. All parameters except `query` ar Parameter | Data type | Description :--- | :--- | :--- `query` | String | The query string to use for search. Required. -`analyzer` | String | The [analyzer]({{site.url}}{{site.baseurl}}/analyzers/index/) used to tokenize the query string text. Default is the index-time analyzer specified for the `default_field`. If no analyzer is specified for the `default_field`, the `analyzer` is the default analyzer for the index. +`analyzer` | String | The [analyzer]({{site.url}}{{site.baseurl}}/analyzers/index/) used to tokenize the query string text. Default is the index-time analyzer specified for the `default_field`. If no analyzer is specified for the `default_field`, the `analyzer` is the default analyzer for the index. For more information about `index.query.default_field`, see [Dynamic index-level index settings]({{site.url}}{{site.baseurl}}/install-and-configure/configuring-opensearch/index-settings/#dynamic-index-level-index-settings). `slop` | `0` (default) or a positive integer | Controls the degree to which words in a query can be misordered and still be considered a match. From the [Lucene documentation](https://lucene.apache.org/core/8_9_0/core/org/apache/lucene/search/PhraseQuery.html#getSlop--): "The number of other words permitted between words in query phrase. For example, to switch the order of two words requires two moves (the first move places the words atop one another), so to permit reorderings of phrases, the slop must be at least two. A value of zero requires an exact match." `zero_terms_query` | String | In some cases, the analyzer removes all terms from a query string. For example, the `stop` analyzer removes all terms from the string `an but this`. In those cases, `zero_terms_query` specifies whether to match no documents (`none`) or all documents (`all`). Valid values are `none` and `all`. Default is `none`. \ No newline at end of file diff --git a/_query-dsl/full-text/match.md b/_query-dsl/full-text/match.md index 056ef76890..5ece14e127 100644 --- a/_query-dsl/full-text/match.md +++ b/_query-dsl/full-text/match.md @@ -451,7 +451,7 @@ Parameter | Data type | Description :--- | :--- | :--- `query` | String | The query string to use for search. Required. `auto_generate_synonyms_phrase_query` | Boolean | Specifies whether to create a [match phrase query]({{site.url}}{{site.baseurl}}/query-dsl/full-text/match-phrase/) automatically for multi-term synonyms. For example, if you specify `ba,batting average` as synonyms and search for `ba`, OpenSearch searches for `ba OR "batting average"` (if this option is `true`) or `ba OR (batting AND average)` (if this option is `false`). Default is `true`. -`analyzer` | String | The [analyzer]({{site.url}}{{site.baseurl}}/analyzers/index/) used to tokenize the query string text. Default is the index-time analyzer specified for the `default_field`. If no analyzer is specified for the `default_field`, the `analyzer` is the default analyzer for the index. +`analyzer` | String | The [analyzer]({{site.url}}{{site.baseurl}}/analyzers/index/) used to tokenize the query string text. Default is the index-time analyzer specified for the `default_field`. If no analyzer is specified for the `default_field`, the `analyzer` is the default analyzer for the index. For more information about `index.query.default_field`, see [Dynamic index-level index settings]({{site.url}}{{site.baseurl}}/install-and-configure/configuring-opensearch/index-settings/#dynamic-index-level-index-settings). `boost` | Floating-point | Boosts the clause by the given multiplier. Useful for weighing clauses in compound queries. Values in the [0, 1) range decrease relevance, and values greater than 1 increase relevance. Default is `1`. `enable_position_increments` | Boolean | When `true`, resulting queries are aware of position increments. This setting is useful when the removal of stop words leaves an unwanted "gap" between terms. Default is `true`. `fuzziness` | String | The number of character edits (insertions, deletions, substitutions, or transpositions) that it takes to change one word to another when determining whether a term matched a value. For example, the distance between `wined` and `wind` is 1. Valid values are non-negative integers or `AUTO`. The default, `AUTO`, chooses a value based on the length of each term and is a good choice for most use cases. diff --git a/_query-dsl/full-text/multi-match.md b/_query-dsl/full-text/multi-match.md index ab1496fdd3..a3995df714 100644 --- a/_query-dsl/full-text/multi-match.md +++ b/_query-dsl/full-text/multi-match.md @@ -900,9 +900,9 @@ Parameter | Data type | Description :--- | :--- | :--- `query` | String | The query string to use for search. Required. `auto_generate_synonyms_phrase_query` | Boolean | Specifies whether to create a [match phrase query]({{site.url}}{{site.baseurl}}/query-dsl/full-text/match-phrase/) automatically for multi-term synonyms. For example, if you specify `ba,batting average` as synonyms and search for `ba`, OpenSearch searches for `ba OR "batting average"` (if this option is `true`) or `ba OR (batting AND average)` (if this option is `false`). Default is `true`. -`analyzer` | String | The [analyzer]({{site.url}}{{site.baseurl}}/analyzers/index/) used to tokenize the query string text. Default is the index-time analyzer specified for the `default_field`. If no analyzer is specified for the `default_field`, the `analyzer` is the default analyzer for the index. +`analyzer` | String | The [analyzer]({{site.url}}{{site.baseurl}}/analyzers/index/) used to tokenize the query string text. Default is the index-time analyzer specified for the `default_field`. If no analyzer is specified for the `default_field`, the `analyzer` is the default analyzer for the index. For more information about `index.query.default_field`, see [Dynamic index-level index settings]({{site.url}}{{site.baseurl}}/install-and-configure/configuring-opensearch/index-settings/#dynamic-index-level-index-settings). `boost` | Floating-point | Boosts the clause by the given multiplier. Useful for weighing clauses in compound queries. Values in the [0, 1) range decrease relevance, and values greater than 1 increase relevance. Default is `1`. -`fields` | Array of strings | The list of fields in which to search. If you don't provide the `fields` parameter, `multi_match` query searches the fields specified in the `index.query. Default_field` setting, which defaults to `*`. +`fields` | Array of strings | The list of fields in which to search. If you don't provide the `fields` parameter, `multi_match` query searches the fields specified in the `index.query.default_field` setting, which defaults to `*`. `fuzziness` | String | The number of character edits (insert, delete, substitute) that it takes to change one word to another when determining whether a term matched a value. For example, the distance between `wined` and `wind` is 1. Valid values are non-negative integers or `AUTO`. The default, `AUTO`, chooses a value based on the length of each term and is a good choice for most use cases. Not supported for `phrase`, `phrase_prefix`, and `cross_fields` queries. `fuzzy_rewrite` | String | Determines how OpenSearch rewrites the query. Valid values are `constant_score`, `scoring_boolean`, `constant_score_boolean`, `top_terms_N`, `top_terms_boost_N`, and `top_terms_blended_freqs_N`. If the `fuzziness` parameter is not `0`, the query uses a `fuzzy_rewrite` method of `top_terms_blended_freqs_${max_expansions}` by default. Default is `constant_score`. `fuzzy_transpositions` | Boolean | Setting `fuzzy_transpositions` to `true` (default) adds swaps of adjacent characters to the insert, delete, and substitute operations of the `fuzziness` option. For example, the distance between `wind` and `wnid` is 1 if `fuzzy_transpositions` is true (swap "n" and "i") and 2 if it is false (delete "n", insert "n"). If `fuzzy_transpositions` is false, `rewind` and `wnid` have the same distance (2) from `wind`, despite the more human-centric opinion that `wnid` is an obvious typo. The default is a good choice for most use cases. diff --git a/_query-dsl/full-text/query-string.md b/_query-dsl/full-text/query-string.md index 47180e3f6d..7b3343155d 100644 --- a/_query-dsl/full-text/query-string.md +++ b/_query-dsl/full-text/query-string.md @@ -623,7 +623,7 @@ Parameter | Data type | Description `query` | String | The text that may contain expressions in the [query string syntax](#query-string-syntax) to use for search. Required. `allow_leading_wildcard` | Boolean | Specifies whether `*` and `?` are allowed as first characters of a search term. Default is `true`. `analyze_wildcard` | Boolean | Specifies whether OpenSearch should attempt to analyze wildcard terms. Default is `false`. -`analyzer` | String | The [analyzer]({{site.url}}{{site.baseurl}}/analyzers/index/) used to tokenize the query string text. Default is the index-time analyzer specified for the `default_field`. If no analyzer is specified for the `default_field`, the `analyzer` is the default analyzer for the index. +`analyzer` | String | The [analyzer]({{site.url}}{{site.baseurl}}/analyzers/index/) used to tokenize the query string text. Default is the index-time analyzer specified for the `default_field`. If no analyzer is specified for the `default_field`, the `analyzer` is the default analyzer for the index. For more information about `index.query.default_field`, see [Dynamic index-level index settings]({{site.url}}{{site.baseurl}}/install-and-configure/configuring-opensearch/index-settings/#dynamic-index-level-index-settings). `auto_generate_synonyms_phrase_query` | Boolean | Specifies whether to create a [match phrase query]({{site.url}}{{site.baseurl}}/query-dsl/full-text/match-phrase/) automatically for multi-term synonyms. For example, if you specify `ba, batting average` as synonyms and search for `ba`, OpenSearch searches for `ba OR "batting average"` (if this option is `true`) or `ba OR (batting AND average)` (if this option is `false`). Default is `true`. `boost` | Floating-point | Boosts the clause by the given multiplier. Useful for weighing clauses in compound queries. Values in the [0, 1) range decrease relevance, and values greater than 1 increase relevance. Default is `1`. `default_field` | String | The field in which to search if the field is not specified in the query string. Supports wildcards. Defaults to the value specified in the `index.query. Default_field` index setting. By default, the `index.query. Default_field` is `*`, which means extract all fields eligible for term query and filter the metadata fields. The extracted fields are combined into a query if the `prefix` is not specified. Eligible fields do not include nested documents. Searching all eligible fields could be a resource-intensive operation. The `indices.query.bool.max_clause_count` search setting defines the maximum value for the product of the number of fields and the number of terms that can be queried at one time. The default value for `indices.query.bool.max_clause_count` is 1,024. diff --git a/_query-dsl/full-text/simple-query-string.md b/_query-dsl/full-text/simple-query-string.md index 5dd2462e9a..1624efdaa7 100644 --- a/_query-dsl/full-text/simple-query-string.md +++ b/_query-dsl/full-text/simple-query-string.md @@ -355,7 +355,7 @@ Parameter | Data type | Description :--- | :--- | :--- `query`| String | The text that may contain expressions in the [simple query string syntax](#simple-query-string-syntax) to use for search. Required. `analyze_wildcard` | Boolean | Specifies whether OpenSearch should attempt to analyze wildcard terms. Default is `false`. -`analyzer` | String | The analyzer used to tokenize the query string text. Default is the index-time analyzer specified for the `default_field`. If no analyzer is specified for the `default_field`, the `analyzer` is the default analyzer for the index. +`analyzer` | String | The analyzer used to tokenize the query string text. Default is the index-time analyzer specified for the `default_field`. If no analyzer is specified for the `default_field`, the `analyzer` is the default analyzer for the index. For more information about `index.query.default_field`, see [Dynamic index-level index settings]({{site.url}}{{site.baseurl}}/install-and-configure/configuring-opensearch/index-settings/#dynamic-index-level-index-settings). `auto_generate_synonyms_phrase_query` | Boolean | Specifies whether to create [match_phrase queries]({{site.url}}{{site.baseurl}}/query-dsl/full-text/match/) automatically for multi-term synonyms. Default is `true`. `default_operator`| String | If the query string contains multiple search terms, whether all terms need to match (`AND`) or only one term needs to match (`OR`) for a document to be considered a match. Valid values are:
- `OR`: The string `to be` is interpreted as `to OR be`
- `AND`: The string `to be` is interpreted as `to AND be`
Default is `OR`. `fields` | String array | The list of fields to search (for example, `"fields": ["title^4", "description"]`). Supports wildcards. If unspecified, defaults to the `index.query.default_field` setting, which defaults to `["*"]`. The maximum number of fields that can be searched at the same time is defined by `indices.query.bool.max_clause_count`, which is 1,024 by default. From 0cb08664b4b2190e9d1da407d68aa37be09ab41d Mon Sep 17 00:00:00 2001 From: AntonEliatra Date: Fri, 6 Dec 2024 13:16:08 +0000 Subject: [PATCH 41/69] adding a page in docs for building a custom analyzer (#8649) * adding a page in docs for building a custom analyzer Signed-off-by: Anton Rubin * Doc review Signed-off-by: Fanit Kolchina * Formatting Signed-off-by: Fanit Kolchina * Add link for custom analyzer Signed-off-by: Fanit Kolchina * Minor rewording Signed-off-by: Fanit Kolchina * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --------- Signed-off-by: Anton Rubin Signed-off-by: Fanit Kolchina Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Fanit Kolchina Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Nathan Bower --- _analyzers/custom-analyzer.md | 312 ++++++++++++++++++++++++++++++++++ _analyzers/index.md | 2 +- _analyzers/normalizers.md | 2 +- 3 files changed, 314 insertions(+), 2 deletions(-) create mode 100644 _analyzers/custom-analyzer.md diff --git a/_analyzers/custom-analyzer.md b/_analyzers/custom-analyzer.md new file mode 100644 index 0000000000..b808268f66 --- /dev/null +++ b/_analyzers/custom-analyzer.md @@ -0,0 +1,312 @@ +--- +layout: default +title: Creating a custom analyzer +nav_order: 90 +parent: Analyzers +--- + +# Creating a custom analyzer + +To create a custom analyzer, specify a combination of the following components: + +- Character filters (zero or more) + +- Tokenizer (one) + +- Token filters (zero or more) + +## Configuration + +The following parameters can be used to configure a custom analyzer. + +| Parameter | Required/Optional | Description | +|:--- | :--- | :--- | +| `type` | Optional | The analyzer type. Default is `custom`. You can also specify a prebuilt analyzer using this parameter. | +| `tokenizer` | Required | A tokenizer to be included in the analyzer. | +| `char_filter` | Optional | A list of character filters to be included in the analyzer. | +| `filter` | Optional | A list of token filters to be included in the analyzer. | +| `position_increment_gap` | Optional | The extra spacing applied between values when indexing text fields that have multiple values. For more information, see [Position increment gap](#position-increment-gap). Default is `100`. | + +## Examples + +The following examples demonstrate various custom analyzer configurations. + +### Custom analyzer with a character filter for HTML stripping + +The following example analyzer removes HTML tags from text before tokenization: + +```json +PUT simple_html_strip_analyzer_index +{ + "settings": { + "analysis": { + "analyzer": { + "html_strip_analyzer": { + "type": "custom", + "char_filter": ["html_strip"], + "tokenizer": "whitespace", + "filter": ["lowercase"] + } + } + } + } +} +``` +{% include copy-curl.html %} + +Use the following request to examine the tokens generated using the analyzer: + +```json +GET simple_html_strip_analyzer_index/_analyze +{ + "analyzer": "html_strip_analyzer", + "text": "

OpenSearch is awesome!

" +} +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + { + "token": "opensearch", + "start_offset": 3, + "end_offset": 13, + "type": "word", + "position": 0 + }, + { + "token": "is", + "start_offset": 14, + "end_offset": 16, + "type": "word", + "position": 1 + }, + { + "token": "awesome!", + "start_offset": 25, + "end_offset": 42, + "type": "word", + "position": 2 + } + ] +} +``` + +### Custom analyzer with a mapping character filter for synonym replacement + +The following example analyzer replaces specific characters and patterns before applying the synonym filter: + +```json +PUT mapping_analyzer_index +{ + "settings": { + "analysis": { + "analyzer": { + "synonym_mapping_analyzer": { + "type": "custom", + "char_filter": ["underscore_to_space"], + "tokenizer": "standard", + "filter": ["lowercase", "stop", "synonym_filter"] + } + }, + "char_filter": { + "underscore_to_space": { + "type": "mapping", + "mappings": ["_ => ' '"] + } + }, + "filter": { + "synonym_filter": { + "type": "synonym", + "synonyms": [ + "quick, fast, speedy", + "big, large, huge" + ] + } + } + } + } +} +``` +{% include copy-curl.html %} + +Use the following request to examine the tokens generated using the analyzer: + +```json +GET mapping_analyzer_index/_analyze +{ + "analyzer": "synonym_mapping_analyzer", + "text": "The slow_green_turtle is very large" +} +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + {"token": "slow","start_offset": 4,"end_offset": 8,"type": "","position": 1}, + {"token": "green","start_offset": 9,"end_offset": 14,"type": "","position": 2}, + {"token": "turtle","start_offset": 15,"end_offset": 21,"type": "","position": 3}, + {"token": "very","start_offset": 25,"end_offset": 29,"type": "","position": 5}, + {"token": "large","start_offset": 30,"end_offset": 35,"type": "","position": 6}, + {"token": "big","start_offset": 30,"end_offset": 35,"type": "SYNONYM","position": 6}, + {"token": "huge","start_offset": 30,"end_offset": 35,"type": "SYNONYM","position": 6} + ] +} +``` + +### Custom analyzer with a custom pattern-based character filter for number normalization + +The following example analyzer normalizes phone numbers by removing dashes and spaces and applies edge n-grams to the normalized text to support partial matches: + +```json +PUT advanced_pattern_replace_analyzer_index +{ + "settings": { + "analysis": { + "analyzer": { + "phone_number_analyzer": { + "type": "custom", + "char_filter": ["phone_normalization"], + "tokenizer": "standard", + "filter": ["lowercase", "edge_ngram"] + } + }, + "char_filter": { + "phone_normalization": { + "type": "pattern_replace", + "pattern": "[-\\s]", + "replacement": "" + } + }, + "filter": { + "edge_ngram": { + "type": "edge_ngram", + "min_gram": 3, + "max_gram": 10 + } + } + } + } +} +``` +{% include copy-curl.html %} + +Use the following request to examine the tokens generated using the analyzer: + +```json +GET advanced_pattern_replace_analyzer_index/_analyze +{ + "analyzer": "phone_number_analyzer", + "text": "123-456 7890" +} +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + {"token": "123","start_offset": 0,"end_offset": 12,"type": "","position": 0}, + {"token": "1234","start_offset": 0,"end_offset": 12,"type": "","position": 0}, + {"token": "12345","start_offset": 0,"end_offset": 12,"type": "","position": 0}, + {"token": "123456","start_offset": 0,"end_offset": 12,"type": "","position": 0}, + {"token": "1234567","start_offset": 0,"end_offset": 12,"type": "","position": 0}, + {"token": "12345678","start_offset": 0,"end_offset": 12,"type": "","position": 0}, + {"token": "123456789","start_offset": 0,"end_offset": 12,"type": "","position": 0}, + {"token": "1234567890","start_offset": 0,"end_offset": 12,"type": "","position": 0} + ] +} +``` + +## Position increment gap + +The `position_increment_gap` parameter sets a positional gap between terms when indexing multi-valued fields, such as arrays. This gap ensures that phrase queries don't match terms across separate values unless explicitly allowed. For example, a default gap of 100 specifies that terms in different array entries are 100 positions apart, preventing unintended matches in phrase searches. You can adjust this value or set it to `0` in order to allow phrases to span across array values. + +The following example demonstrates the effect of `position_increment_gap` using a `match_phrase` query. + +1. Index a document in a `test-index`: + + ```json + PUT test-index/_doc/1 + { + "names": [ "Slow green", "turtle swims"] + } + ``` + {% include copy-curl.html %} + +1. Query the document using a `match_phrase` query: + + ```json + GET test-index/_search + { + "query": { + "match_phrase": { + "names": { + "query": "green turtle" + } + } + } + } + ``` + {% include copy-curl.html %} + + The response returns no hits because the distance between the terms `green` and `turtle` is `100` (the default `position_increment_gap`). + +1. Now query the document using a `match_phrase` query with a `slop` parameter that is higher than the `position_increment_gap`: + + ```json + GET test-index/_search + { + "query": { + "match_phrase": { + "names": { + "query": "green turtle", + "slop": 101 + } + } + } + } + ``` + {% include copy-curl.html %} + + The response contains the matching document: + + ```json + { + "took": 4, + "timed_out": false, + "_shards": { + "total": 1, + "successful": 1, + "skipped": 0, + "failed": 0 + }, + "hits": { + "total": { + "value": 1, + "relation": "eq" + }, + "max_score": 0.010358453, + "hits": [ + { + "_index": "test-index", + "_id": "1", + "_score": 0.010358453, + "_source": { + "names": [ + "Slow green", + "turtle swims" + ] + } + } + ] + } + } + ``` diff --git a/_analyzers/index.md b/_analyzers/index.md index def6563f3e..1dc38b2cd4 100644 --- a/_analyzers/index.md +++ b/_analyzers/index.md @@ -51,7 +51,7 @@ For a list of supported analyzers, see [Analyzers]({{site.url}}{{site.baseurl}}/ ## Custom analyzers -If needed, you can combine tokenizers, token filters, and character filters to create a custom analyzer. +If needed, you can combine tokenizers, token filters, and character filters to create a custom analyzer. For more information, see [Creating a custom analyzer]({{site.url}}{{site.baseurl}}/analyzers/custom-analyzer/). ## Text analysis at indexing time and query time diff --git a/_analyzers/normalizers.md b/_analyzers/normalizers.md index b89659f814..52841d2571 100644 --- a/_analyzers/normalizers.md +++ b/_analyzers/normalizers.md @@ -1,7 +1,7 @@ --- layout: default title: Normalizers -nav_order: 100 +nav_order: 110 --- # Normalizers From e770b6123ac80535b411c6064d45248cb5637b37 Mon Sep 17 00:00:00 2001 From: Johannes Peter Date: Fri, 6 Dec 2024 15:57:15 +0100 Subject: [PATCH 42/69] Improved documentation of supported model types in LTR plugin (#8831) * Enhanced LTR documentation by explicitly mentioning other supported RankLib models and showing RankNet Example Signed-off-by: Johannes Peter * Update _search-plugins/ltr/training-models.md Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Signed-off-by: Johannes Peter * Update _search-plugins/ltr/training-models.md Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Signed-off-by: Johannes Peter * Update _search-plugins/ltr/training-models.md Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Signed-off-by: Johannes Peter * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --------- Signed-off-by: Johannes Peter Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Nathan Bower --- _search-plugins/ltr/training-models.md | 31 ++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/_search-plugins/ltr/training-models.md b/_search-plugins/ltr/training-models.md index 7bee5d10ac..fb068cedd7 100644 --- a/_search-plugins/ltr/training-models.md +++ b/_search-plugins/ltr/training-models.md @@ -16,10 +16,10 @@ The feature logging process generates a RankLib-comsumable judgment file. In the score) and 2 (a description `TF*IDF` score) for a set of documents: ``` -4 qid:1 1:9.8376875 2:12.318446 # 7555 rambo -3 qid:1 1:10.7808075 2:9.510193 # 1370 rambo -3 qid:1 1:10.7808075 2:6.8449354 # 1369 rambo -3 qid:1 1:10.7808075 2:0.0 # 1368 rambo +4 qid:1 1:9.8376875 2:12.318446 # 7555 rambo +3 qid:1 1:10.7808075 2:9.510193 # 1370 rambo +3 qid:1 1:10.7808075 2:6.8449354 # 1369 rambo +3 qid:1 1:10.7808075 2:0.0 # 1368 rambo ``` The RankLib library can be called using the following command: @@ -49,6 +49,29 @@ RankLib outputs the model in its own serialization format. As shown in the follo Within the RankLib model, each tree in the ensemble examines feature values, makes decisions based on these feature values, and outputs the relevance scores. The features are referred to by their ordinal position, starting from 1, which corresponds to the 0th feature in the original feature set. RankLib does not use feature names during model training. +### Other RankLib models + +RankLib is a library that implements several other model types in addition to LambdaMART, such as MART, +RankNet, RankBoost, AdaRank, Coordinate Ascent, ListNet, and Random Forests. Each of these models has its own set of parameters and training process. + +For example, the RankNet model is a neural network that learns to predict the probability of a document being more relevant than another document. The model is trained using a pairwise loss function that compares the predicted relevance of two documents with the actual relevance. The model is serialized in a format similar to the following example: + +``` +## RankNet +## Epochs = 100 +## No. of features = 5 +## No. of hidden layers = 1 +... +## Layer 1: 10 neurons +1 2 +1 +10 +0 0 -0.013491530393429608 0.031183180961270988 0.06558792020112071 -0.006024092627087733 0.05729619574181734 -0.0017010373987742411 0.07684848696852313 -0.06570387602230028 0.04390491141617467 0.013371636736099578 +... +``` + +All these models can be used with the Learning to Rank plugin, provided that the model is serialized in the RankLib format. + ## XGBoost model training Unlike the RankLib model, the XGBoost model is serialized in a format specific to gradient-boosted decision trees, as shown in the following example: From 17809aa129d95c31ebc7588282f4105801ab2c1c Mon Sep 17 00:00:00 2001 From: Nathan Bower Date: Fri, 6 Dec 2024 11:41:48 -0500 Subject: [PATCH 43/69] Editorial review of Query Group Lifecycle API (#8893) Signed-off-by: natebower <102320899+natebower@users.noreply.github.com> Co-authored-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> --- .../query-group-lifecycle-api.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/_tuning-your-cluster/availability-and-recovery/workload-management/query-group-lifecycle-api.md b/_tuning-your-cluster/availability-and-recovery/workload-management/query-group-lifecycle-api.md index 83d66c9eb8..0fb0b0b65c 100644 --- a/_tuning-your-cluster/availability-and-recovery/workload-management/query-group-lifecycle-api.md +++ b/_tuning-your-cluster/availability-and-recovery/workload-management/query-group-lifecycle-api.md @@ -1,6 +1,6 @@ --- layout: default -title: Query group lifecycle API +title: Query Group Lifecycle API nav_order: 20 parent: Workload management grand_parent: Availability and recovery @@ -8,9 +8,9 @@ grand_parent: Availability and recovery # Query Group Lifecycle API -The Query Group Lifecycle API in creates, updates, retrieves, and deletes query groups. The API categorizes queries into specific groups, called _query groups_ based on desired resource limits. +The Query Group Lifecycle API creates, updates, retrieves, and deletes query groups. The API categorizes queries into specific groups, called _query groups_, based on desired resource limits. -## Paths and HTTP method +## Path and HTTP methods ```json PUT _wlm/query_group @@ -33,7 +33,7 @@ When creating a query group, make sure that the sum of the resource limits for a ## Example requests -The following requests show how to use the Query Group Lifecycle API. +The following example requests show how to use the Query Group Lifecycle API. ### Creating a query group @@ -48,6 +48,7 @@ PUT _wlm/query_group } } ``` +{% include copy-curl.html %} ### Updating a query group @@ -61,18 +62,21 @@ PUT _wlm/query_group/analytics } } ``` +{% include copy-curl.html %} ### Getting a query group ```json GET _wlm/query_group/analytics ``` +{% include copy-curl.html %} ### Deleting a query group ```json DELETE _wlm/query_group/analytics ``` +{% include copy-curl.html %} ## Example responses @@ -93,7 +97,7 @@ OpenSearch returns responses similar to the following. } ``` -### Updating query group +### Updating a query group ```json { From e764e405f3bd3f9c1d28b8f17f88f258a9f37cbc Mon Sep 17 00:00:00 2001 From: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Date: Fri, 6 Dec 2024 12:29:30 -0500 Subject: [PATCH 44/69] Update pr_checklist.yml (#8897) Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --- .github/workflows/pr_checklist.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr_checklist.yml b/.github/workflows/pr_checklist.yml index b56174793e..e34d0cecb2 100644 --- a/.github/workflows/pr_checklist.yml +++ b/.github/workflows/pr_checklist.yml @@ -29,7 +29,7 @@ jobs: with: script: | let assignee = context.payload.pull_request.user.login; - const prOwners = ['Naarcha-AWS', 'kolchfa-aws', 'vagimeli', 'natebower']; + const prOwners = ['Naarcha-AWS', 'kolchfa-aws', 'natebower']; if (!prOwners.includes(assignee)) { assignee = 'kolchfa-aws' @@ -40,4 +40,4 @@ jobs: owner: context.repo.owner, repo: context.repo.repo, assignees: [assignee] - }); \ No newline at end of file + }); From a32933a8d3bfa9ecc09b6ddd1233b583ad0b6ef0 Mon Sep 17 00:00:00 2001 From: AntonEliatra Date: Mon, 9 Dec 2024 17:11:47 +0000 Subject: [PATCH 45/69] add whitespace tokenizer docs (#8484) * add whitespace tokenizer docs Signed-off-by: Anton Rubin * Update whitespace.md Signed-off-by: AntonEliatra * Update whitespace.md Signed-off-by: AntonEliatra * Update whitespace.md Signed-off-by: AntonEliatra * updating parameter table Signed-off-by: Anton Rubin * Doc review Signed-off-by: Fanit Kolchina * Update _analyzers/tokenizers/whitespace.md Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --------- Signed-off-by: Anton Rubin Signed-off-by: AntonEliatra Signed-off-by: Fanit Kolchina Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Fanit Kolchina Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Nathan Bower --- _analyzers/tokenizers/index.md | 2 +- _analyzers/tokenizers/whitespace.md | 110 ++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 _analyzers/tokenizers/whitespace.md diff --git a/_analyzers/tokenizers/index.md b/_analyzers/tokenizers/index.md index e5ac796c12..1f9e49c855 100644 --- a/_analyzers/tokenizers/index.md +++ b/_analyzers/tokenizers/index.md @@ -2,7 +2,7 @@ layout: default title: Tokenizers nav_order: 60 -has_children: false +has_children: true has_toc: false redirect_from: - /analyzers/tokenizers/index/ diff --git a/_analyzers/tokenizers/whitespace.md b/_analyzers/tokenizers/whitespace.md new file mode 100644 index 0000000000..604eeeb6a0 --- /dev/null +++ b/_analyzers/tokenizers/whitespace.md @@ -0,0 +1,110 @@ +--- +layout: default +title: Whitespace +parent: Tokenizers +nav_order: 160 +--- + +# Whitespace tokenizer + +The `whitespace` tokenizer splits text at white space characters, such as spaces, tabs, and new lines. It treats each word separated by white space as a token and does not perform any additional analysis or normalization like lowercasing or punctuation removal. + +## Example usage + +The following example request creates a new index named `my_index` and configures an analyzer with a `whitespace` tokenizer: + +```json +PUT /my_index +{ + "settings": { + "analysis": { + "tokenizer": { + "whitespace_tokenizer": { + "type": "whitespace" + } + }, + "analyzer": { + "my_whitespace_analyzer": { + "type": "custom", + "tokenizer": "whitespace_tokenizer" + } + } + } + }, + "mappings": { + "properties": { + "content": { + "type": "text", + "analyzer": "my_whitespace_analyzer" + } + } + } +} +``` +{% include copy-curl.html %} + +## Generated tokens + +Use the following request to examine the tokens generated using the analyzer: + +```json +POST /my_index/_analyze +{ + "analyzer": "my_whitespace_analyzer", + "text": "OpenSearch is fast! Really fast." +} +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + { + "token": "OpenSearch", + "start_offset": 0, + "end_offset": 10, + "type": "word", + "position": 0 + }, + { + "token": "is", + "start_offset": 11, + "end_offset": 13, + "type": "word", + "position": 1 + }, + { + "token": "fast!", + "start_offset": 14, + "end_offset": 19, + "type": "word", + "position": 2 + }, + { + "token": "Really", + "start_offset": 20, + "end_offset": 26, + "type": "word", + "position": 3 + }, + { + "token": "fast.", + "start_offset": 27, + "end_offset": 32, + "type": "word", + "position": 4 + } + ] +} +``` + +## Parameters + +The `whitespace` tokenizer can be configured with the following parameter. + +Parameter | Required/Optional | Data type | Description +:--- | :--- | :--- | :--- +`max_token_length` | Optional | Integer | Sets the maximum length of the produced token. If this length is exceeded, the token is split into multiple tokens at the length configured in `max_token_length`. Default is `255`. + From 42b97bf53f8e46779aaef3e91ca210ea5c10b733 Mon Sep 17 00:00:00 2001 From: AntonEliatra Date: Mon, 9 Dec 2024 17:17:58 +0000 Subject: [PATCH 46/69] add uax_url_email tokenizer docs (#8485) * add uax_url_email tokenizer docs Signed-off-by: Anton Rubin * Update uax-url-email.md Signed-off-by: AntonEliatra * Update uax-url-email.md Signed-off-by: AntonEliatra * updating parameter table Signed-off-by: Anton Rubin * Doc review Signed-off-by: Fanit Kolchina * Update _analyzers/tokenizers/uax-url-email.md Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --------- Signed-off-by: Anton Rubin Signed-off-by: AntonEliatra Signed-off-by: Fanit Kolchina Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Fanit Kolchina Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Nathan Bower --- _analyzers/tokenizers/uax-url-email.md | 84 ++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 _analyzers/tokenizers/uax-url-email.md diff --git a/_analyzers/tokenizers/uax-url-email.md b/_analyzers/tokenizers/uax-url-email.md new file mode 100644 index 0000000000..34336a4f55 --- /dev/null +++ b/_analyzers/tokenizers/uax-url-email.md @@ -0,0 +1,84 @@ +--- +layout: default +title: UAX URL email +parent: Tokenizers +nav_order: 150 +--- + +# UAX URL email tokenizer + +In addition to regular text, the `uax_url_email` tokenizer is designed to handle URLs, email addresses, and domain names. It is based on the Unicode Text Segmentation algorithm ([UAX #29](https://www.unicode.org/reports/tr29/)), which allows it to correctly tokenize complex text, including URLs and email addresses. + +## Example usage + +The following example request creates a new index named `my_index` and configures an analyzer with a `uax_url_email` tokenizer: + +```json +PUT /my_index +{ + "settings": { + "analysis": { + "tokenizer": { + "uax_url_email_tokenizer": { + "type": "uax_url_email" + } + }, + "analyzer": { + "my_uax_analyzer": { + "type": "custom", + "tokenizer": "uax_url_email_tokenizer" + } + } + } + }, + "mappings": { + "properties": { + "content": { + "type": "text", + "analyzer": "my_uax_analyzer" + } + } + } +} +``` +{% include copy-curl.html %} + +## Generated tokens + +Use the following request to examine the tokens generated using the analyzer: + +```json +POST /my_index/_analyze +{ + "analyzer": "my_uax_analyzer", + "text": "Contact us at support@example.com or visit https://example.com for details." +} +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + {"token": "Contact","start_offset": 0,"end_offset": 7,"type": "","position": 0}, + {"token": "us","start_offset": 8,"end_offset": 10,"type": "","position": 1}, + {"token": "at","start_offset": 11,"end_offset": 13,"type": "","position": 2}, + {"token": "support@example.com","start_offset": 14,"end_offset": 33,"type": "","position": 3}, + {"token": "or","start_offset": 34,"end_offset": 36,"type": "","position": 4}, + {"token": "visit","start_offset": 37,"end_offset": 42,"type": "","position": 5}, + {"token": "https://example.com","start_offset": 43,"end_offset": 62,"type": "","position": 6}, + {"token": "for","start_offset": 63,"end_offset": 66,"type": "","position": 7}, + {"token": "details","start_offset": 67,"end_offset": 74,"type": "","position": 8} + ] +} +``` + +## Parameters + +The `uax_url_email` tokenizer can be configured with the following parameter. + +Parameter | Required/Optional | Data type | Description +:--- | :--- | :--- | :--- +`max_token_length` | Optional | Integer | Sets the maximum length of the produced token. If this length is exceeded, the token is split into multiple tokens at the length configured in `max_token_length`. Default is `255`. + From 5e338e6de51750c509da33f04f32344e6d3149b9 Mon Sep 17 00:00:00 2001 From: AntonEliatra Date: Mon, 9 Dec 2024 17:23:46 +0000 Subject: [PATCH 47/69] add thai tokenizer docs (#8489) * add thai tokenizer docs Signed-off-by: Anton Rubin * Doc review Signed-off-by: Fanit Kolchina * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --------- Signed-off-by: Anton Rubin Signed-off-by: Fanit Kolchina Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Fanit Kolchina Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Nathan Bower --- _analyzers/tokenizers/thai.md | 108 ++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 _analyzers/tokenizers/thai.md diff --git a/_analyzers/tokenizers/thai.md b/_analyzers/tokenizers/thai.md new file mode 100644 index 0000000000..4afb14a9eb --- /dev/null +++ b/_analyzers/tokenizers/thai.md @@ -0,0 +1,108 @@ +--- +layout: default +title: Thai +parent: Tokenizers +nav_order: 140 +--- + +# Thai tokenizer + +The `thai` tokenizer tokenizes Thai language text. Because words in Thai language are not separated by spaces, the tokenizer must identify word boundaries based on language-specific rules. + +## Example usage + +The following example request creates a new index named `thai_index` and configures an analyzer with a `thai` tokenizer: + +```json +PUT /thai_index +{ + "settings": { + "analysis": { + "tokenizer": { + "thai_tokenizer": { + "type": "thai" + } + }, + "analyzer": { + "thai_analyzer": { + "type": "custom", + "tokenizer": "thai_tokenizer" + } + } + } + }, + "mappings": { + "properties": { + "content": { + "type": "text", + "analyzer": "thai_analyzer" + } + } + } +} +``` +{% include copy-curl.html %} + +## Generated tokens + +Use the following request to examine the tokens generated using the analyzer: + +```json +POST /thai_index/_analyze +{ + "analyzer": "thai_analyzer", + "text": "ฉันชอบไปเที่ยวที่เชียงใหม่" +} +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + { + "token": "ฉัน", + "start_offset": 0, + "end_offset": 3, + "type": "word", + "position": 0 + }, + { + "token": "ชอบ", + "start_offset": 3, + "end_offset": 6, + "type": "word", + "position": 1 + }, + { + "token": "ไป", + "start_offset": 6, + "end_offset": 8, + "type": "word", + "position": 2 + }, + { + "token": "เที่ยว", + "start_offset": 8, + "end_offset": 14, + "type": "word", + "position": 3 + }, + { + "token": "ที่", + "start_offset": 14, + "end_offset": 17, + "type": "word", + "position": 4 + }, + { + "token": "เชียงใหม่", + "start_offset": 17, + "end_offset": 26, + "type": "word", + "position": 5 + } + ] +} +``` From dbc14967791cb21297d1c83dc5cc1e949f13050b Mon Sep 17 00:00:00 2001 From: AntonEliatra Date: Mon, 9 Dec 2024 17:30:53 +0000 Subject: [PATCH 48/69] add standard tokenizer docs (#8490) * add standard tokenizer docs Signed-off-by: Anton Rubin * updating parameter table Signed-off-by: Anton Rubin * Doc review Signed-off-by: Fanit Kolchina * Update _analyzers/tokenizers/standard.md Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --------- Signed-off-by: Anton Rubin Signed-off-by: Fanit Kolchina Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Fanit Kolchina Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Nathan Bower --- _analyzers/tokenizers/standard.md | 111 ++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 _analyzers/tokenizers/standard.md diff --git a/_analyzers/tokenizers/standard.md b/_analyzers/tokenizers/standard.md new file mode 100644 index 0000000000..c10f25802b --- /dev/null +++ b/_analyzers/tokenizers/standard.md @@ -0,0 +1,111 @@ +--- +layout: default +title: Standard +parent: Tokenizers +nav_order: 130 +--- + +# Standard tokenizer + +The `standard` tokenizer is the default tokenizer in OpenSearch. It tokenizes text based on word boundaries using a grammar-based approach that recognizes letters, digits, and other characters like punctuation. It is highly versatile and suitable for many languages because it uses Unicode text segmentation rules ([UAX#29](https://unicode.org/reports/tr29/)) to break text into tokens. + +## Example usage + +The following example request creates a new index named `my_index` and configures an analyzer with a `standard` tokenizer: + +```json +PUT /my_index +{ + "settings": { + "analysis": { + "analyzer": { + "my_standard_analyzer": { + "type": "standard" + } + } + } + }, + "mappings": { + "properties": { + "content": { + "type": "text", + "analyzer": "my_standard_analyzer" + } + } + } +} +``` +{% include copy-curl.html %} + +## Generated tokens + +Use the following request to examine the tokens generated using the analyzer: + +```json +POST /my_index/_analyze +{ + "analyzer": "my_standard_analyzer", + "text": "OpenSearch is powerful, fast, and scalable." +} +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + { + "token": "opensearch", + "start_offset": 0, + "end_offset": 10, + "type": "", + "position": 0 + }, + { + "token": "is", + "start_offset": 11, + "end_offset": 13, + "type": "", + "position": 1 + }, + { + "token": "powerful", + "start_offset": 14, + "end_offset": 22, + "type": "", + "position": 2 + }, + { + "token": "fast", + "start_offset": 24, + "end_offset": 28, + "type": "", + "position": 3 + }, + { + "token": "and", + "start_offset": 30, + "end_offset": 33, + "type": "", + "position": 4 + }, + { + "token": "scalable", + "start_offset": 34, + "end_offset": 42, + "type": "", + "position": 5 + } + ] +} +``` + +## Parameters + +The `standard` tokenizer can be configured with the following parameter. + +Parameter | Required/Optional | Data type | Description +:--- | :--- | :--- | :--- +`max_token_length` | Optional | Integer | Sets the maximum length of the produced token. If this length is exceeded, the token is split into multiple tokens at the length configured in `max_token_length`. Default is `255`. + From 595bc1302bc12ca8fc9e83b9d1c2136960b165ae Mon Sep 17 00:00:00 2001 From: AntonEliatra Date: Mon, 9 Dec 2024 17:53:03 +0000 Subject: [PATCH 49/69] add simple pattern split tokenizer docs (#8491) * add simple pattern split tokenizer docs Signed-off-by: Anton Rubin * updating parameter table Signed-off-by: Anton Rubin * Doc review Signed-off-by: Fanit Kolchina * Update _analyzers/tokenizers/simple-pattern-split.md Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> * Clarification Signed-off-by: Fanit Kolchina * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --------- Signed-off-by: Anton Rubin Signed-off-by: Fanit Kolchina Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Fanit Kolchina Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Nathan Bower --- _analyzers/tokenizers/simple-pattern-split.md | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 _analyzers/tokenizers/simple-pattern-split.md diff --git a/_analyzers/tokenizers/simple-pattern-split.md b/_analyzers/tokenizers/simple-pattern-split.md new file mode 100644 index 0000000000..1fd130082e --- /dev/null +++ b/_analyzers/tokenizers/simple-pattern-split.md @@ -0,0 +1,105 @@ +--- +layout: default +title: Simple pattern split +parent: Tokenizers +nav_order: 120 +--- + +# Simple pattern split tokenizer + +The `simple_pattern_split` tokenizer uses a regular expression to split text into tokens. The regular expression defines the pattern used to determine where to split the text. Any matching pattern in the text is used as a delimiter, and the text between delimiters becomes a token. Use this tokenizer when you want to define delimiters and tokenize the rest of the text based on a pattern. + +The tokenizer uses the matched parts of the input text (based on the regular expression) only as delimiters or boundaries to split the text into terms. The matched portions are not included in the resulting terms. For example, if the tokenizer is configured to split text at dot characters (`.`) and the input text is `one.two.three`, then the generated terms are `one`, `two`, and `three`. The dot characters themselves are not included in the resulting terms. + +## Example usage + +The following example request creates a new index named `my_index` and configures an analyzer with a `simple_pattern_split` tokenizer. The tokenizer is configured to split text at hyphens: + +```json +PUT /my_index +{ + "settings": { + "analysis": { + "tokenizer": { + "my_pattern_split_tokenizer": { + "type": "simple_pattern_split", + "pattern": "-" + } + }, + "analyzer": { + "my_pattern_split_analyzer": { + "type": "custom", + "tokenizer": "my_pattern_split_tokenizer" + } + } + } + }, + "mappings": { + "properties": { + "content": { + "type": "text", + "analyzer": "my_pattern_split_analyzer" + } + } + } +} +``` +{% include copy-curl.html %} + +## Generated tokens + +Use the following request to examine the tokens generated using the analyzer: + +```json +POST /my_index/_analyze +{ + "analyzer": "my_pattern_split_analyzer", + "text": "OpenSearch-2024-10-09" +} +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + { + "token": "OpenSearch", + "start_offset": 0, + "end_offset": 10, + "type": "word", + "position": 0 + }, + { + "token": "2024", + "start_offset": 11, + "end_offset": 15, + "type": "word", + "position": 1 + }, + { + "token": "10", + "start_offset": 16, + "end_offset": 18, + "type": "word", + "position": 2 + }, + { + "token": "09", + "start_offset": 19, + "end_offset": 21, + "type": "word", + "position": 3 + } + ] +} +``` + +## Parameters + +The `simple_pattern_split` tokenizer can be configured with the following parameter. + +Parameter | Required/Optional | Data type | Description +:--- | :--- | :--- | :--- +`pattern` | Optional | String | The pattern used to split text into tokens, specified using a [Lucene regular expression](https://lucene.apache.org/core/9_10_0/core/org/apache/lucene/util/automaton/RegExp.html). Default is an empty string, which returns the input text as one token. \ No newline at end of file From bf4698070f15a320b60711c92ffc6b46c105d488 Mon Sep 17 00:00:00 2001 From: AntonEliatra Date: Mon, 9 Dec 2024 17:58:06 +0000 Subject: [PATCH 50/69] add simple pattern tokenizer docs (#8492) * add simple pattern tokenizer docs Signed-off-by: Anton Rubin * updating parameter table Signed-off-by: Anton Rubin * Doc review Signed-off-by: Fanit Kolchina * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --------- Signed-off-by: Anton Rubin Signed-off-by: Fanit Kolchina Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Fanit Kolchina Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Nathan Bower --- _analyzers/tokenizers/simple-pattern.md | 89 +++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 _analyzers/tokenizers/simple-pattern.md diff --git a/_analyzers/tokenizers/simple-pattern.md b/_analyzers/tokenizers/simple-pattern.md new file mode 100644 index 0000000000..eacddd6992 --- /dev/null +++ b/_analyzers/tokenizers/simple-pattern.md @@ -0,0 +1,89 @@ +--- +layout: default +title: Simple pattern +parent: Tokenizers +nav_order: 110 +--- + +# Simple pattern tokenizer + +The `simple_pattern` tokenizer identifies matching sequences in text based on a regular expression and uses those sequences as tokens. It extracts terms that match the regular expression. Use this tokenizer when you want to directly extract specific patterns as terms. + +## Example usage + +The following example request creates a new index named `my_index` and configures an analyzer with a `simple_pattern` tokenizer. The tokenizer extracts numeric terms from text: + +```json +PUT /my_index +{ + "settings": { + "analysis": { + "tokenizer": { + "my_pattern_tokenizer": { + "type": "simple_pattern", + "pattern": "\\d+" + } + }, + "analyzer": { + "my_pattern_analyzer": { + "type": "custom", + "tokenizer": "my_pattern_tokenizer" + } + } + } + } +} +``` +{% include copy-curl.html %} + +## Generated tokens + +Use the following request to examine the tokens generated using the analyzer: + +```json +POST /my_index/_analyze +{ + "analyzer": "my_pattern_analyzer", + "text": "OpenSearch-2024-10-09" +} +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + { + "token": "2024", + "start_offset": 11, + "end_offset": 15, + "type": "word", + "position": 0 + }, + { + "token": "10", + "start_offset": 16, + "end_offset": 18, + "type": "word", + "position": 1 + }, + { + "token": "09", + "start_offset": 19, + "end_offset": 21, + "type": "word", + "position": 2 + } + ] +} +``` + +## Parameters + +The `simple_pattern` tokenizer can be configured with the following parameter. + +Parameter | Required/Optional | Data type | Description +:--- | :--- | :--- | :--- +`pattern` | Optional | String | The pattern used to split text into tokens, specified using a [Lucene regular expression](https://lucene.apache.org/core/9_10_0/core/org/apache/lucene/util/automaton/RegExp.html). Default is an empty string, which returns the input text as one token. + From 8c3568d2d99594dba85a6d1fe62890882b48a5d6 Mon Sep 17 00:00:00 2001 From: AntonEliatra Date: Mon, 9 Dec 2024 18:01:59 +0000 Subject: [PATCH 51/69] add pattern tokenizer docs (#8493) * add pattern tokenizer docs Signed-off-by: Anton Rubin * updating parameter table Signed-off-by: Anton Rubin * Doc review Signed-off-by: Fanit Kolchina * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --------- Signed-off-by: Anton Rubin Signed-off-by: Fanit Kolchina Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Fanit Kolchina Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Nathan Bower --- _analyzers/tokenizers/pattern.md | 167 +++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 _analyzers/tokenizers/pattern.md diff --git a/_analyzers/tokenizers/pattern.md b/_analyzers/tokenizers/pattern.md new file mode 100644 index 0000000000..f422d8c805 --- /dev/null +++ b/_analyzers/tokenizers/pattern.md @@ -0,0 +1,167 @@ +--- +layout: default +title: Pattern +parent: Tokenizers +nav_order: 100 +--- + +# Pattern tokenizer + +The `pattern` tokenizer is a highly flexible tokenizer that allows you to split text into tokens based on a custom Java regular expression. Unlike the `simple_pattern` and `simple_pattern_split` tokenizers, which use Lucene regular expressions, the `pattern` tokenizer can handle more complex and detailed regex patterns, offering greater control over how the text is tokenized. + +## Example usage + +The following example request creates a new index named `my_index` and configures an analyzer with a `pattern` tokenizer. The tokenizer splits text at `-`, `_`, or `.` characters: + +```json +PUT /my_index +{ + "settings": { + "analysis": { + "tokenizer": { + "my_pattern_tokenizer": { + "type": "pattern", + "pattern": "[-_.]" + } + }, + "analyzer": { + "my_pattern_analyzer": { + "type": "custom", + "tokenizer": "my_pattern_tokenizer" + } + } + } + }, + "mappings": { + "properties": { + "content": { + "type": "text", + "analyzer": "my_pattern_analyzer" + } + } + } +} +``` +{% include copy-curl.html %} + +## Generated tokens + +Use the following request to examine the tokens generated using the analyzer: + +```json +POST /my_index/_analyze +{ + "analyzer": "my_pattern_analyzer", + "text": "OpenSearch-2024_v1.2" +} +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + { + "token": "OpenSearch", + "start_offset": 0, + "end_offset": 10, + "type": "word", + "position": 0 + }, + { + "token": "2024", + "start_offset": 11, + "end_offset": 15, + "type": "word", + "position": 1 + }, + { + "token": "v1", + "start_offset": 16, + "end_offset": 18, + "type": "word", + "position": 2 + }, + { + "token": "2", + "start_offset": 19, + "end_offset": 20, + "type": "word", + "position": 3 + } + ] +} +``` + +## Parameters + +The `pattern` tokenizer can be configured with the following parameters. + +Parameter | Required/Optional | Data type | Description +:--- | :--- | :--- | :--- +`pattern` | Optional | String | The pattern used to split text into tokens, specified using a [Java regular expression](https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html). Default is `\W+`. +`flags` | Optional | String | Configures pipe-separated [flags](https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html#field.summary) to apply to the regular expression, for example, `"CASE_INSENSITIVE|MULTILINE|DOTALL"`. +`group` | Optional | Integer | Specifies the capture group to be used as a token. Default is `-1` (split at a match). + +## Example using a group parameter + +The following example request configures a `group` parameter that captures only the second group: + +```json +PUT /my_index_group2 +{ + "settings": { + "analysis": { + "tokenizer": { + "my_pattern_tokenizer": { + "type": "pattern", + "pattern": "([a-zA-Z]+)(\\d+)", + "group": 2 + } + }, + "analyzer": { + "my_pattern_analyzer": { + "type": "custom", + "tokenizer": "my_pattern_tokenizer" + } + } + } + } +} +``` +{% include copy-curl.html %} + +Use the following request to examine the tokens generated using the analyzer: + +```json +POST /my_index_group2/_analyze +{ + "analyzer": "my_pattern_analyzer", + "text": "abc123def456ghi" +} +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + { + "token": "123", + "start_offset": 3, + "end_offset": 6, + "type": "word", + "position": 0 + }, + { + "token": "456", + "start_offset": 9, + "end_offset": 12, + "type": "word", + "position": 1 + } + ] +} +``` \ No newline at end of file From face7c23cb3c1204ae74d4000345ab344876156e Mon Sep 17 00:00:00 2001 From: AntonEliatra Date: Mon, 9 Dec 2024 18:07:06 +0000 Subject: [PATCH 52/69] add path hierarchy tokenizer docs (#8494) * add path-hierarchy-tokenizer-docs Signed-off-by: Anton Rubin * updating parameter table Signed-off-by: Anton Rubin * Doc review Signed-off-by: Fanit Kolchina * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --------- Signed-off-by: Anton Rubin Signed-off-by: Fanit Kolchina Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Fanit Kolchina Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Nathan Bower --- _analyzers/tokenizers/path-hierarchy.md | 182 ++++++++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 _analyzers/tokenizers/path-hierarchy.md diff --git a/_analyzers/tokenizers/path-hierarchy.md b/_analyzers/tokenizers/path-hierarchy.md new file mode 100644 index 0000000000..a6609f30cd --- /dev/null +++ b/_analyzers/tokenizers/path-hierarchy.md @@ -0,0 +1,182 @@ +--- +layout: default +title: Path hierarchy +parent: Tokenizers +nav_order: 90 +--- + +# Path hierarchy tokenizer + +The `path_hierarchy` tokenizer tokenizes file-system-like paths (or similar hierarchical structures) by breaking them down into tokens at each hierarchy level. This tokenizer is particularly useful when working with hierarchical data such as file paths, URLs, or any other delimited paths. + +## Example usage + +The following example request creates a new index named `my_index` and configures an analyzer with a `path_hierarchy` tokenizer: + +```json +PUT /my_index +{ + "settings": { + "analysis": { + "tokenizer": { + "my_path_tokenizer": { + "type": "path_hierarchy" + } + }, + "analyzer": { + "my_path_analyzer": { + "type": "custom", + "tokenizer": "my_path_tokenizer" + } + } + } + } +} +``` +{% include copy-curl.html %} + +## Generated tokens + +Use the following request to examine the tokens generated using the analyzer: + +```json +POST /my_index/_analyze +{ + "analyzer": "my_path_analyzer", + "text": "/users/john/documents/report.txt" +} +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + { + "token": "/users", + "start_offset": 0, + "end_offset": 6, + "type": "word", + "position": 0 + }, + { + "token": "/users/john", + "start_offset": 0, + "end_offset": 11, + "type": "word", + "position": 0 + }, + { + "token": "/users/john/documents", + "start_offset": 0, + "end_offset": 21, + "type": "word", + "position": 0 + }, + { + "token": "/users/john/documents/report.txt", + "start_offset": 0, + "end_offset": 32, + "type": "word", + "position": 0 + } + ] +} +``` + +## Parameters + +The `path_hierarchy` tokenizer can be configured with the following parameters. + +Parameter | Required/Optional | Data type | Description +:--- | :--- | :--- | :--- +`delimiter` | Optional | String | Specifies the character used to separate path components. Default is `/`. +`replacement` | Optional | String | Configures the character used to replace the delimiter in the tokens. Default is `/`. +`buffer_size` | Optional | Integer | Specifies the buffer size. Default is `1024`. +`reverse` | Optional | Boolean | If `true`, generates tokens in reverse order. Default is `false`. +`skip` | Optional | Integer | Specifies the number of initial tokens (levels) to skip when tokenizing. Default is `0`. + +## Example using delimiter and replacement parameters + +The following example request configures custom `delimiter` and `replacement` parameters: + +```json +PUT /my_index +{ + "settings": { + "analysis": { + "tokenizer": { + "my_path_tokenizer": { + "type": "path_hierarchy", + "delimiter": "\\", + "replacement": "\\" + } + }, + "analyzer": { + "my_path_analyzer": { + "type": "custom", + "tokenizer": "my_path_tokenizer" + } + } + } + } +} +``` +{% include copy-curl.html %} + + +Use the following request to examine the tokens generated using the analyzer: + +```json +POST /my_index/_analyze +{ + "analyzer": "my_path_analyzer", + "text": "C:\\users\\john\\documents\\report.txt" +} +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + { + "token": "C:", + "start_offset": 0, + "end_offset": 2, + "type": "word", + "position": 0 + }, + { + "token": """C:\users""", + "start_offset": 0, + "end_offset": 8, + "type": "word", + "position": 0 + }, + { + "token": """C:\users\john""", + "start_offset": 0, + "end_offset": 13, + "type": "word", + "position": 0 + }, + { + "token": """C:\users\john\documents""", + "start_offset": 0, + "end_offset": 23, + "type": "word", + "position": 0 + }, + { + "token": """C:\users\john\documents\report.txt""", + "start_offset": 0, + "end_offset": 34, + "type": "word", + "position": 0 + } + ] +} +``` \ No newline at end of file From dd38bbe54b7fef9cf4ebce89fac9063bc2178f72 Mon Sep 17 00:00:00 2001 From: gaobinlong Date: Tue, 10 Dec 2024 02:09:15 +0800 Subject: [PATCH 53/69] Document the usage of update document API with ingest pipeline (#8874) * Document the usage of update document API with ingest pipeline Signed-off-by: gaobinlong * Update _api-reference/document-apis/update-document.md Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Signed-off-by: gaobinlong --------- Signed-off-by: gaobinlong Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --- _api-reference/document-apis/update-document.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/_api-reference/document-apis/update-document.md b/_api-reference/document-apis/update-document.md index ff17940cdb..8500fec101 100644 --- a/_api-reference/document-apis/update-document.md +++ b/_api-reference/document-apis/update-document.md @@ -14,6 +14,14 @@ redirect_from: If you need to update a document's fields in your index, you can use the update document API operation. You can do so by specifying the new data you want to be in your index or by including a script in your request body, which OpenSearch runs to update the document. By default, the update operation only updates a document that exists in the index. If a document does not exist, the API returns an error. To _upsert_ a document (update the document that exists or index a new one), use the [upsert](#using-the-upsert-operation) operation. +You cannot explicitly specify an ingest pipeline when calling the Update Document API. If a `default_pipeline` or `final_pipeline` is defined in your index, the following behavior applies: + +- **Upsert operations**: When indexing a new document, the `default_pipeline` and `final_pipeline` defined in the index are executed as specified. +- **Update operations**: When updating an existing document, ingest pipeline execution is not recommended because it may produce erroneous results. Support for running ingest pipelines during update operations is deprecated and will be removed in version 3.0.0. If your index has a defined ingest pipeline, the update document operation will return the following deprecation warning: +``` +the index [sample-index1] has a default ingest pipeline or a final ingest pipeline, the support of the ingest pipelines for update operation causes unexpected result and will be removed in 3.0.0 +``` + ## Path and HTTP methods ```json From b203c4cd639f040b1243cd39cf0cfca929cbffde Mon Sep 17 00:00:00 2001 From: AntonEliatra Date: Mon, 9 Dec 2024 18:19:14 +0000 Subject: [PATCH 54/69] add ngram tokenizer docs (#8496) * add ngram tokenizer docs Signed-off-by: Anton Rubin * updating parameter table Signed-off-by: Anton Rubin * Doc review Signed-off-by: Fanit Kolchina * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --------- Signed-off-by: Anton Rubin Signed-off-by: Fanit Kolchina Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Fanit Kolchina Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Nathan Bower --- _analyzers/tokenizers/ngram.md | 111 +++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 _analyzers/tokenizers/ngram.md diff --git a/_analyzers/tokenizers/ngram.md b/_analyzers/tokenizers/ngram.md new file mode 100644 index 0000000000..08ac456267 --- /dev/null +++ b/_analyzers/tokenizers/ngram.md @@ -0,0 +1,111 @@ +--- +layout: default +title: N-gram +parent: Tokenizers +nav_order: 80 +--- + +# N-gram tokenizer + +The `ngram` tokenizer splits text into overlapping n-grams (sequences of characters) of a specified length. This tokenizer is particularly useful when you want to perform partial word matching or autocomplete search functionality because it generates substrings (character n-grams) of the original input text. + +## Example usage + +The following example request creates a new index named `my_index` and configures an analyzer with an `ngram` tokenizer: + +```json +PUT /my_index +{ + "settings": { + "analysis": { + "tokenizer": { + "my_ngram_tokenizer": { + "type": "ngram", + "min_gram": 3, + "max_gram": 4, + "token_chars": ["letter", "digit"] + } + }, + "analyzer": { + "my_ngram_analyzer": { + "type": "custom", + "tokenizer": "my_ngram_tokenizer" + } + } + } + } +} +``` +{% include copy-curl.html %} + +## Generated tokens + +Use the following request to examine the tokens generated using the analyzer: + +```json +POST /my_index/_analyze +{ + "analyzer": "my_ngram_analyzer", + "text": "OpenSearch" +} +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + {"token": "Sea","start_offset": 0,"end_offset": 3,"type": "word","position": 0}, + {"token": "Sear","start_offset": 0,"end_offset": 4,"type": "word","position": 1}, + {"token": "ear","start_offset": 1,"end_offset": 4,"type": "word","position": 2}, + {"token": "earc","start_offset": 1,"end_offset": 5,"type": "word","position": 3}, + {"token": "arc","start_offset": 2,"end_offset": 5,"type": "word","position": 4}, + {"token": "arch","start_offset": 2,"end_offset": 6,"type": "word","position": 5}, + {"token": "rch","start_offset": 3,"end_offset": 6,"type": "word","position": 6} + ] +} +``` + +## Parameters + +The `ngram` tokenizer can be configured with the following parameters. + +Parameter | Required/Optional | Data type | Description +:--- | :--- | :--- | :--- +`min_gram` | Optional | Integer | The minimum length of the n-grams. Default is `1`. +`max_gram` | Optional | Integer | The maximum length of the n-grams. Default is `2`. +`token_chars` | Optional | List of strings | The character classes to be included in tokenization. Valid values are:
- `letter`
- `digit`
- `whitespace`
- `punctuation`
- `symbol`
- `custom` (You must also specify the `custom_token_chars` parameter)
Default is an empty list (`[]`), which retains all the characters. +`custom_token_chars` | Optional | String | Custom characters to be included in the tokens. + +### Maximum difference between `min_gram` and `max_gram` + +The maximum difference between `min_gram` and `max_gram` is configured using the index-level `index.max_ngram_diff` setting and defaults to `1`. + +The following example request creates an index with a custom `index.max_ngram_diff` setting: + +```json +PUT /my-index +{ + "settings": { + "index.max_ngram_diff": 2, + "analysis": { + "tokenizer": { + "my_ngram_tokenizer": { + "type": "ngram", + "min_gram": 3, + "max_gram": 5, + "token_chars": ["letter", "digit"] + } + }, + "analyzer": { + "my_ngram_analyzer": { + "type": "custom", + "tokenizer": "my_ngram_tokenizer" + } + } + } + } +} +``` +{% include copy-curl.html %} From 68a8af5fcf24d846b72a3024a88bce5c58c829e6 Mon Sep 17 00:00:00 2001 From: AntonEliatra Date: Mon, 9 Dec 2024 18:23:49 +0000 Subject: [PATCH 55/69] add lowercase tokenizer docs (#8497) * add lowercase tokenizer docs Signed-off-by: Anton Rubin * Doc review Signed-off-by: Fanit Kolchina * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --------- Signed-off-by: Anton Rubin Signed-off-by: Fanit Kolchina Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Fanit Kolchina Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Nathan Bower --- _analyzers/tokenizers/lowercase.md | 93 ++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 _analyzers/tokenizers/lowercase.md diff --git a/_analyzers/tokenizers/lowercase.md b/_analyzers/tokenizers/lowercase.md new file mode 100644 index 0000000000..5542ecbf50 --- /dev/null +++ b/_analyzers/tokenizers/lowercase.md @@ -0,0 +1,93 @@ +--- +layout: default +title: Lowercase +parent: Tokenizers +nav_order: 70 +--- + +# Lowercase tokenizer + +The `lowercase` tokenizer breaks text into terms at white space and then lowercases all the terms. Functionally, this is identical to configuring a `letter` tokenizer with a `lowercase` token filter. However, using a `lowercase` tokenizer is more efficient because the tokenizer actions are performed in a single step. + +## Example usage + +The following example request creates a new index named `my-lowercase-index` and configures an analyzer with a `lowercase` tokenizer: + +```json +PUT /my-lowercase-index +{ + "settings": { + "analysis": { + "tokenizer": { + "my_lowercase_tokenizer": { + "type": "lowercase" + } + }, + "analyzer": { + "my_lowercase_analyzer": { + "type": "custom", + "tokenizer": "my_lowercase_tokenizer" + } + } + } + } +} +``` +{% include copy-curl.html %} + +## Generated tokens + +Use the following request to examine the tokens generated using the analyzer: + +```json +POST /my-lowercase-index/_analyze +{ + "analyzer": "my_lowercase_analyzer", + "text": "This is a Test. OpenSearch 123!" +} +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + { + "token": "this", + "start_offset": 0, + "end_offset": 4, + "type": "word", + "position": 0 + }, + { + "token": "is", + "start_offset": 5, + "end_offset": 7, + "type": "word", + "position": 1 + }, + { + "token": "a", + "start_offset": 8, + "end_offset": 9, + "type": "word", + "position": 2 + }, + { + "token": "test", + "start_offset": 10, + "end_offset": 14, + "type": "word", + "position": 3 + }, + { + "token": "opensearch", + "start_offset": 16, + "end_offset": 26, + "type": "word", + "position": 4 + } + ] +} +``` From acb1e9f622a3f6ab119d4f6e6474128260d33a20 Mon Sep 17 00:00:00 2001 From: mbo57 <96461698+mbo57@users.noreply.github.com> Date: Tue, 10 Dec 2024 05:10:40 +0900 Subject: [PATCH 56/69] Changed the default value of expand for synonym and synonym_graph to true. (#8898) * Update synonym.md Signed-off-by: mbo57 <96461698+mbo57@users.noreply.github.com> * Update synonym-graph.md Signed-off-by: mbo57 <96461698+mbo57@users.noreply.github.com> --------- Signed-off-by: mbo57 <96461698+mbo57@users.noreply.github.com> Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --- _analyzers/token-filters/synonym-graph.md | 2 +- _analyzers/token-filters/synonym.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/_analyzers/token-filters/synonym-graph.md b/_analyzers/token-filters/synonym-graph.md index 75c7c79151..d8e763d1fc 100644 --- a/_analyzers/token-filters/synonym-graph.md +++ b/_analyzers/token-filters/synonym-graph.md @@ -19,7 +19,7 @@ Parameter | Required/Optional | Data type | Description `synonyms_path` | Either `synonyms` or `synonyms_path` must be specified | String | The file path to a file containing synonym rules (either an absolute path or a path relative to the config directory). `lenient` | Optional | Boolean | Whether to ignore exceptions when loading the rule configurations. Default is `false`. `format` | Optional | String | Specifies the format used to determine how OpenSearch defines and interprets synonyms. Valid values are:
- `solr`
- [`wordnet`](https://wordnet.princeton.edu/).
Default is `solr`. -`expand` | Optional | Boolean | Whether to expand equivalent synonym rules. Default is `false`.

For example:
If `synonyms` are defined as `"quick, fast"` and `expand` is set to `true`, then the synonym rules are configured as follows:
- `quick => quick`
- `quick => fast`
- `fast => quick`
- `fast => fast`

If `expand` is set to `false`, the synonym rules are configured as follows:
- `quick => quick`
- `fast => quick` +`expand` | Optional | Boolean | Whether to expand equivalent synonym rules. Default is `true`.

For example:
If `synonyms` are defined as `"quick, fast"` and `expand` is set to `true`, then the synonym rules are configured as follows:
- `quick => quick`
- `quick => fast`
- `fast => quick`
- `fast => fast`

If `expand` is set to `false`, the synonym rules are configured as follows:
- `quick => quick`
- `fast => quick` ## Example: Solr format diff --git a/_analyzers/token-filters/synonym.md b/_analyzers/token-filters/synonym.md index 296d5cd5db..a1dfff845d 100644 --- a/_analyzers/token-filters/synonym.md +++ b/_analyzers/token-filters/synonym.md @@ -19,7 +19,7 @@ Parameter | Required/Optional | Data type | Description `synonyms_path` | Either `synonyms` or `synonyms_path` must be specified | String | The file path to a file containing synonym rules (either an absolute path or a path relative to the config directory). `lenient` | Optional | Boolean | Whether to ignore exceptions when loading the rule configurations. Default is `false`. `format` | Optional | String | Specifies the format used to determine how OpenSearch defines and interprets synonyms. Valid values are:
- `solr`
- [`wordnet`](https://wordnet.princeton.edu/).
Default is `solr`. -`expand` | Optional | Boolean | Whether to expand equivalent synonym rules. Default is `false`.

For example:
If `synonyms` are defined as `"quick, fast"` and `expand` is set to `true`, then the synonym rules are configured as follows:
- `quick => quick`
- `quick => fast`
- `fast => quick`
- `fast => fast`

If `expand` is set to `false`, the synonym rules are configured as follows:
- `quick => quick`
- `fast => quick` +`expand` | Optional | Boolean | Whether to expand equivalent synonym rules. Default is `true`.

For example:
If `synonyms` are defined as `"quick, fast"` and `expand` is set to `true`, then the synonym rules are configured as follows:
- `quick => quick`
- `quick => fast`
- `fast => quick`
- `fast => fast`

If `expand` is set to `false`, the synonym rules are configured as follows:
- `quick => quick`
- `fast => quick` ## Example: Solr format From b81500f8c22dff2f6951a78217be43fd449f8635 Mon Sep 17 00:00:00 2001 From: AntonEliatra Date: Tue, 10 Dec 2024 14:05:40 +0000 Subject: [PATCH 57/69] Adding standard analyzer docs (#8528) * adding standard analyzer docs Signed-off-by: Anton Rubin * adding further details Signed-off-by: Anton Rubin * adding more examples Signed-off-by: Anton Rubin * Doc review Signed-off-by: Fanit Kolchina * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --------- Signed-off-by: Anton Rubin Signed-off-by: Fanit Kolchina Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Fanit Kolchina Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Nathan Bower --- _analyzers/standard.md | 96 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 _analyzers/standard.md diff --git a/_analyzers/standard.md b/_analyzers/standard.md new file mode 100644 index 0000000000..e4a7a70fbc --- /dev/null +++ b/_analyzers/standard.md @@ -0,0 +1,96 @@ +--- +layout: default +title: Standard analyzer +nav_order: 40 +--- + +# Standard analyzer + +The `standard` analyzer is the default analyzer used when no other analyzer is specified. It is designed to provide a basic and efficient approach to generic text processing. + +This analyzer consists of the following tokenizers and token filters: + +- `standard` tokenizer: Removes most punctuation and splits text on spaces and other common delimiters. +- `lowercase` token filter: Converts all tokens to lowercase, ensuring case-insensitive matching. +- `stop` token filter: Removes common stopwords, such as "the", "is", and "and", from the tokenized output. + +## Example + +Use the following command to create an index named `my_standard_index` with a `standard` analyzer: + +```json +PUT /my_standard_index +{ + "mappings": { + "properties": { + "my_field": { + "type": "text", + "analyzer": "standard" + } + } + } +} +``` +{% include copy-curl.html %} + +## Parameters + +You can configure a `standard` analyzer with the following parameters. + +Parameter | Required/Optional | Data type | Description +:--- | :--- | :--- | :--- +`max_token_length` | Optional | Integer | Sets the maximum length of the produced token. If this length is exceeded, the token is split into multiple tokens at the length configured in `max_token_length`. Default is `255`. +`stopwords` | Optional | String or list of strings | A string specifying a predefined list of stopwords (such as `_english_`) or an array specifying a custom list of stopwords. Default is `_none_`. +`stopwords_path` | Optional | String | The path (absolute or relative to the config directory) to the file containing a list of stop words. + + +## Configuring a custom analyzer + +Use the following command to configure an index with a custom analyzer that is equivalent to the `standard` analyzer: + +```json +PUT /my_custom_index +{ + "settings": { + "analysis": { + "analyzer": { + "my_custom_analyzer": { + "type": "custom", + "tokenizer": "standard", + "filter": [ + "lowercase", + "stop" + ] + } + } + } + } +} +``` +{% include copy-curl.html %} + +## Generated tokens + +Use the following request to examine the tokens generated using the analyzer: + +```json +POST /my_custom_index/_analyze +{ + "analyzer": "my_custom_analyzer", + "text": "The slow turtle swims away" +} +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + {"token": "slow","start_offset": 4,"end_offset": 8,"type": "","position": 1}, + {"token": "turtle","start_offset": 9,"end_offset": 15,"type": "","position": 2}, + {"token": "swims","start_offset": 16,"end_offset": 21,"type": "","position": 3}, + {"token": "away","start_offset": 22,"end_offset": 26,"type": "","position": 4} + ] +} +``` From 450581326dc2805236ae01ac4d4dee2a2b201741 Mon Sep 17 00:00:00 2001 From: AntonEliatra Date: Tue, 10 Dec 2024 15:22:06 +0000 Subject: [PATCH 58/69] add simple analyzer docs (#8529) * add simple analyzer docs Signed-off-by: Anton Rubin * adding more examples Signed-off-by: Anton Rubin * Doc review Signed-off-by: Fanit Kolchina * Update _analyzers/simple.md Co-authored-by: Nathan Bower Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --------- Signed-off-by: Anton Rubin Signed-off-by: Fanit Kolchina Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Fanit Kolchina Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Nathan Bower --- _analyzers/simple.md | 98 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 _analyzers/simple.md diff --git a/_analyzers/simple.md b/_analyzers/simple.md new file mode 100644 index 0000000000..edfa7f58a6 --- /dev/null +++ b/_analyzers/simple.md @@ -0,0 +1,98 @@ +--- +layout: default +title: Simple analyzer +nav_order: 50 +--- + +# Simple analyzer + +The `simple` analyzer is a very basic analyzer that breaks text into terms at non-letter characters and lowercases the terms. Unlike the `standard` analyzer, the `simple` analyzer treats everything except for alphabetic characters as delimiters, meaning that it does not recognize numbers, punctuation, or special characters as part of the tokens. + +## Example + +Use the following command to create an index named `my_simple_index` with a `simple` analyzer: + +```json +PUT /my_simple_index +{ + "mappings": { + "properties": { + "my_field": { + "type": "text", + "analyzer": "simple" + } + } + } +} +``` +{% include copy-curl.html %} + +## Configuring a custom analyzer + +Use the following command to configure an index with a custom analyzer that is equivalent to a `simple` analyzer with an added `html_strip` character filter: + +```json +PUT /my_custom_simple_index +{ + "settings": { + "analysis": { + "char_filter": { + "html_strip": { + "type": "html_strip" + } + }, + "tokenizer": { + "my_lowercase_tokenizer": { + "type": "lowercase" + } + }, + "analyzer": { + "my_custom_simple_analyzer": { + "type": "custom", + "char_filter": ["html_strip"], + "tokenizer": "my_lowercase_tokenizer", + "filter": ["lowercase"] + } + } + } + }, + "mappings": { + "properties": { + "my_field": { + "type": "text", + "analyzer": "my_custom_simple_analyzer" + } + } + } +} +``` +{% include copy-curl.html %} + +## Generated tokens + +Use the following request to examine the tokens generated using the analyzer: + +```json +POST /my_custom_simple_index/_analyze +{ + "analyzer": "my_custom_simple_analyzer", + "text": "

The slow turtle swims over to dogs © 2024!

" +} +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + {"token": "the","start_offset": 3,"end_offset": 6,"type": "word","position": 0}, + {"token": "slow","start_offset": 7,"end_offset": 11,"type": "word","position": 1}, + {"token": "turtle","start_offset": 12,"end_offset": 18,"type": "word","position": 2}, + {"token": "swims","start_offset": 19,"end_offset": 24,"type": "word","position": 3}, + {"token": "over","start_offset": 25,"end_offset": 29,"type": "word","position": 4}, + {"token": "to","start_offset": 30,"end_offset": 32,"type": "word","position": 5}, + {"token": "dogs","start_offset": 33,"end_offset": 37,"type": "word","position": 6} + ] +} +``` From 8693afbf56f43270980ecf8b4af59940af37830a Mon Sep 17 00:00:00 2001 From: AntonEliatra Date: Tue, 10 Dec 2024 15:26:45 +0000 Subject: [PATCH 59/69] add whitespace analyzer docs (#8531) * add whitespace analyzer docs Signed-off-by: Anton Rubin * Doc review Signed-off-by: Fanit Kolchina * Update _analyzers/whitespace.md Co-authored-by: Nathan Bower Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --------- Signed-off-by: Anton Rubin Signed-off-by: Fanit Kolchina Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Fanit Kolchina Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Nathan Bower --- _analyzers/whitespace.md | 86 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 _analyzers/whitespace.md diff --git a/_analyzers/whitespace.md b/_analyzers/whitespace.md new file mode 100644 index 0000000000..67fee61295 --- /dev/null +++ b/_analyzers/whitespace.md @@ -0,0 +1,86 @@ +--- +layout: default +title: Whitespace analyzer +nav_order: 60 +--- + +# Whitespace analyzer + +The `whitespace` analyzer breaks text into tokens based only on white space characters (for example, spaces and tabs). It does not apply any transformations, such as lowercasing or removing stopwords, so the original case of the text is retained and punctuation is included as part of the tokens. + +## Example + +Use the following command to create an index named `my_whitespace_index` with a `whitespace` analyzer: + +```json +PUT /my_whitespace_index +{ + "mappings": { + "properties": { + "my_field": { + "type": "text", + "analyzer": "whitespace" + } + } + } +} +``` +{% include copy-curl.html %} + +## Configuring a custom analyzer + +Use the following command to configure an index with a custom analyzer that is equivalent to a `whitespace` analyzer with an added `lowercase` character filter: + +```json +PUT /my_custom_whitespace_index +{ + "settings": { + "analysis": { + "analyzer": { + "my_custom_whitespace_analyzer": { + "type": "custom", + "tokenizer": "whitespace", + "filter": ["lowercase"] + } + } + } + }, + "mappings": { + "properties": { + "my_field": { + "type": "text", + "analyzer": "my_custom_whitespace_analyzer" + } + } + } +} +``` +{% include copy-curl.html %} + +## Generated tokens + +Use the following request to examine the tokens generated using the analyzer: + +```json +POST /my_custom_whitespace_index/_analyze +{ + "analyzer": "my_custom_whitespace_analyzer", + "text": "The SLOW turtle swims away! 123" +} +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + {"token": "the","start_offset": 0,"end_offset": 3,"type": "word","position": 0}, + {"token": "slow","start_offset": 4,"end_offset": 8,"type": "word","position": 1}, + {"token": "turtle","start_offset": 9,"end_offset": 15,"type": "word","position": 2}, + {"token": "swims","start_offset": 16,"end_offset": 21,"type": "word","position": 3}, + {"token": "away!","start_offset": 22,"end_offset": 27,"type": "word","position": 4}, + {"token": "123","start_offset": 28,"end_offset": 31,"type": "word","position": 5} + ] +} +``` From 7d1151ccd11823e714835e07bbdd2cbb6448ccb8 Mon Sep 17 00:00:00 2001 From: AntonEliatra Date: Tue, 10 Dec 2024 15:52:58 +0000 Subject: [PATCH 60/69] add stop analyzer docs (#8534) * add stop analyzer docs Signed-off-by: Anton Rubin * adding more details Signed-off-by: Anton Rubin * updating parameter table Signed-off-by: Anton Rubin * Doc review Signed-off-by: Fanit Kolchina * Update _analyzers/stop.md Co-authored-by: Nathan Bower Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> * Update _analyzers/stop.md Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> * Add file path example Signed-off-by: Fanit Kolchina --------- Signed-off-by: Anton Rubin Signed-off-by: Fanit Kolchina Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Fanit Kolchina Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Nathan Bower --- _analyzers/stop.md | 176 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 _analyzers/stop.md diff --git a/_analyzers/stop.md b/_analyzers/stop.md new file mode 100644 index 0000000000..68dc554473 --- /dev/null +++ b/_analyzers/stop.md @@ -0,0 +1,176 @@ +--- +layout: default +title: Stop analyzer +nav_order: 70 +--- + +# Stop analyzer + +The `stop` analyzer removes a predefined list of stopwords. This analyzer consists of a `lowercase` tokenizer and a `stop` token filter. + +## Parameters + +You can configure a `stop` analyzer with the following parameters. + +Parameter | Required/Optional | Data type | Description +:--- | :--- | :--- | :--- +`stopwords` | Optional | String or list of strings | A string specifying a predefined list of stopwords (such as `_english_`) or an array specifying a custom list of stopwords. Default is `_english_`. +`stopwords_path` | Optional | String | The path (absolute or relative to the config directory) to the file containing a list of stopwords. + +## Example + +Use the following command to create an index named `my_stop_index` with a `stop` analyzer: + +```json +PUT /my_stop_index +{ + "mappings": { + "properties": { + "my_field": { + "type": "text", + "analyzer": "stop" + } + } + } +} +``` +{% include copy-curl.html %} + +## Configuring a custom analyzer + +Use the following command to configure an index with a custom analyzer that is equivalent to a `stop` analyzer: + +```json +PUT /my_custom_stop_analyzer_index +{ + "settings": { + "analysis": { + "analyzer": { + "my_custom_stop_analyzer": { + "tokenizer": "lowercase", + "filter": [ + "stop" + ] + } + } + } + }, + "mappings": { + "properties": { + "my_field": { + "type": "text", + "analyzer": "my_custom_stop_analyzer" + } + } + } +} +``` +{% include copy-curl.html %} + +## Generated tokens + +Use the following request to examine the tokens generated using the analyzer: + +```json +POST /my_custom_stop_analyzer_index/_analyze +{ + "analyzer": "my_custom_stop_analyzer", + "text": "The large turtle is green and brown" +} +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + { + "token": "large", + "start_offset": 4, + "end_offset": 9, + "type": "word", + "position": 1 + }, + { + "token": "turtle", + "start_offset": 10, + "end_offset": 16, + "type": "word", + "position": 2 + }, + { + "token": "green", + "start_offset": 20, + "end_offset": 25, + "type": "word", + "position": 4 + }, + { + "token": "brown", + "start_offset": 30, + "end_offset": 35, + "type": "word", + "position": 6 + } + ] +} +``` + +# Specifying stopwords + +The following example request specifies a custom list of stopwords: + +```json +PUT /my_new_custom_stop_index +{ + "settings": { + "analysis": { + "analyzer": { + "my_custom_stop_analyzer": { + "type": "stop", + "stopwords": ["is", "and", "was"] + } + } + } + }, + "mappings": { + "properties": { + "description": { + "type": "text", + "analyzer": "my_custom_stop_analyzer" + } + } + } +} +``` +{% include copy-curl.html %} + +The following example request specifies a path to the file containing stopwords: + +```json +PUT /my_new_custom_stop_index +{ + "settings": { + "analysis": { + "analyzer": { + "my_custom_stop_analyzer": { + "type": "stop", + "stopwords_path": "stopwords.txt" + } + } + } + }, + "mappings": { + "properties": { + "description": { + "type": "text", + "analyzer": "my_custom_stop_analyzer" + } + } + } +} +``` +{% include copy-curl.html %} + +In this example, the file is located in the config directory. You can also specify a full path to the file. \ No newline at end of file From a6cc16bc0f3a3f86cc94cd199f5bb4847ffbafa2 Mon Sep 17 00:00:00 2001 From: AntonEliatra Date: Tue, 10 Dec 2024 15:58:24 +0000 Subject: [PATCH 61/69] add keyword analyzer docs (#8535) * add keyword analyzer docs Signed-off-by: Anton Rubin * Doc review Signed-off-by: Fanit Kolchina * Update _analyzers/keyword.md Co-authored-by: Nathan Bower Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --------- Signed-off-by: Anton Rubin Signed-off-by: Fanit Kolchina Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Fanit Kolchina Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Nathan Bower --- _analyzers/keyword.md | 77 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 _analyzers/keyword.md diff --git a/_analyzers/keyword.md b/_analyzers/keyword.md new file mode 100644 index 0000000000..3aec99d1d4 --- /dev/null +++ b/_analyzers/keyword.md @@ -0,0 +1,77 @@ +--- +layout: default +title: Keyword analyzer +nav_order: 80 +--- + +# Keyword analyzer + +The `keyword` analyzer doesn't tokenize text at all. Instead, it treats the entire input as a single token and does not break it into individual tokens. The `keyword` analyzer is often used for fields containing email addresses, URLs, or product IDs and in other cases where tokenization is not desirable. + +## Example + +Use the following command to create an index named `my_keyword_index` with a `keyword` analyzer: + +```json +PUT /my_keyword_index +{ + "mappings": { + "properties": { + "my_field": { + "type": "text", + "analyzer": "keyword" + } + } + } +} +``` +{% include copy-curl.html %} + +## Configuring a custom analyzer + +Use the following command to configure an index with a custom analyzer that is equivalent to the `keyword` analyzer: + +```json +PUT /my_custom_keyword_index +{ + "settings": { + "analysis": { + "analyzer": { + "my_keyword_analyzer": { + "tokenizer": "keyword" + } + } + } + } +} +``` +{% include copy-curl.html %} + +## Generated tokens + +Use the following request to examine the tokens generated using the analyzer: + +```json +POST /my_custom_keyword_index/_analyze +{ + "analyzer": "my_keyword_analyzer", + "text": "Just one token" +} +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + { + "token": "Just one token", + "start_offset": 0, + "end_offset": 14, + "type": "word", + "position": 0 + } + ] +} +``` From 76f14ef3fe8281cbc674655ac22aba060685a51c Mon Sep 17 00:00:00 2001 From: AntonEliatra Date: Tue, 10 Dec 2024 16:02:19 +0000 Subject: [PATCH 62/69] add pattern analyzer docs (#8536) * add pattern analyzer docs Signed-off-by: Anton Rubin * Update pattern.md Signed-off-by: AntonEliatra * updating parameter table Signed-off-by: Anton Rubin * Doc review Signed-off-by: Fanit Kolchina * Update _analyzers/pattern.md Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --------- Signed-off-by: Anton Rubin Signed-off-by: AntonEliatra Signed-off-by: Fanit Kolchina Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Fanit Kolchina Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Nathan Bower --- _analyzers/pattern.md | 96 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 _analyzers/pattern.md diff --git a/_analyzers/pattern.md b/_analyzers/pattern.md new file mode 100644 index 0000000000..0d67999b82 --- /dev/null +++ b/_analyzers/pattern.md @@ -0,0 +1,96 @@ +--- +layout: default +title: Pattern analyzer +nav_order: 90 +--- + +# Pattern analyzer + +The `pattern` analyzer allows you to define a custom analyzer that uses a regular expression (regex) to split input text into tokens. It also provides options for applying regex flags, converting tokens to lowercase, and filtering out stopwords. + +## Parameters + +The `pattern` analyzer can be configured with the following parameters. + +Parameter | Required/Optional | Data type | Description +:--- | :--- | :--- | :--- +`pattern` | Optional | String | A [Java regular expression](https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html) used to tokenize the input. Default is `\W+`. +`flags` | Optional | String | A string containing pipe-separated [Java regex flags](https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html#field.summary) that modify the behavior of the regular expression. +`lowercase` | Optional | Boolean | Whether to convert tokens to lowercase. Default is `true`. +`stopwords` | Optional | String or list of strings | A string specifying a predefined list of stopwords (such as `_english_`) or an array specifying a custom list of stopwords. Default is `_none_`. +`stopwords_path` | Optional | String | The path (absolute or relative to the config directory) to the file containing a list of stopwords. + + +## Example + +Use the following command to create an index named `my_pattern_index` with a `pattern` analyzer: + +```json +PUT /my_pattern_index +{ + "settings": { + "analysis": { + "analyzer": { + "my_pattern_analyzer": { + "type": "pattern", + "pattern": "\\W+", + "lowercase": true, + "stopwords": ["and", "is"] + } + } + } + }, + "mappings": { + "properties": { + "my_field": { + "type": "text", + "analyzer": "my_pattern_analyzer" + } + } + } +} +``` +{% include copy-curl.html %} + +## Generated tokens + +Use the following request to examine the tokens generated using the analyzer: + +```json +POST /my_pattern_index/_analyze +{ + "analyzer": "my_pattern_analyzer", + "text": "OpenSearch is fast and scalable" +} +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + { + "token": "opensearch", + "start_offset": 0, + "end_offset": 10, + "type": "word", + "position": 0 + }, + { + "token": "fast", + "start_offset": 14, + "end_offset": 18, + "type": "word", + "position": 2 + }, + { + "token": "scalable", + "start_offset": 23, + "end_offset": 31, + "type": "word", + "position": 4 + } + ] +} +``` From bc30bbda39ffae7b6b177035310ab03e08ba228b Mon Sep 17 00:00:00 2001 From: AntonEliatra Date: Tue, 10 Dec 2024 16:02:32 +0000 Subject: [PATCH 63/69] add fingerprint analyzer docs (#8537) * add fingerprint analyzer docs Signed-off-by: Anton Rubin * updating parameter table Signed-off-by: Anton Rubin * Doc review Signed-off-by: Fanit Kolchina * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --------- Signed-off-by: Anton Rubin Signed-off-by: Fanit Kolchina Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Fanit Kolchina Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Nathan Bower --- _analyzers/fingerprint.md | 114 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 _analyzers/fingerprint.md diff --git a/_analyzers/fingerprint.md b/_analyzers/fingerprint.md new file mode 100644 index 0000000000..dd8027f037 --- /dev/null +++ b/_analyzers/fingerprint.md @@ -0,0 +1,114 @@ +--- +layout: default +title: Fingerprint analyzer +nav_order: 110 +--- + +# Fingerprint analyzer + +The `fingerprint` analyzer creates a text fingerprint. The analyzer sorts and deduplicates the terms (tokens) generated from the input and then concatenates them using a separator. It is commonly used for data deduplication because it produces the same output for similar inputs containing the same words, regardless of word order. + +The `fingerprint` analyzer comprises the following components: + +- Standard tokenizer +- Lowercase token filter +- ASCII folding token filter +- Stop token filter +- Fingerprint token filter + +## Parameters + +The `fingerprint` analyzer can be configured with the following parameters. + +Parameter | Required/Optional | Data type | Description +:--- | :--- | :--- | :--- +`separator` | Optional | String | Specifies the character used to concatenate the terms after they have been tokenized, sorted, and deduplicated. Default is an empty space (` `). +`max_output_size` | Optional | Integer | Defines the maximum size of the output token. If the concatenated fingerprint exceeds this size, it will be truncated. Default is `255`. +`stopwords` | Optional | String or list of strings | A custom or predefined list of stopwords. Default is `_none_`. +`stopwords_path` | Optional | String | The path (absolute or relative to the config directory) to the file containing a list of stopwords. + + +## Example + +Use the following command to create an index named `my_custom_fingerprint_index` with a `fingerprint` analyzer: + +```json +PUT /my_custom_fingerprint_index +{ + "settings": { + "analysis": { + "analyzer": { + "my_custom_fingerprint_analyzer": { + "type": "fingerprint", + "separator": "-", + "max_output_size": 50, + "stopwords": ["to", "the", "over", "and"] + } + } + } + }, + "mappings": { + "properties": { + "my_field": { + "type": "text", + "analyzer": "my_custom_fingerprint_analyzer" + } + } + } +} +``` +{% include copy-curl.html %} + +## Generated tokens + +Use the following request to examine the tokens generated using the analyzer: + +```json +POST /my_custom_fingerprint_index/_analyze +{ + "analyzer": "my_custom_fingerprint_analyzer", + "text": "The slow turtle swims over to the dog" +} +``` +{% include copy-curl.html %} + +The response contains the generated tokens: + +```json +{ + "tokens": [ + { + "token": "dog-slow-swims-turtle", + "start_offset": 0, + "end_offset": 37, + "type": "fingerprint", + "position": 0 + } + ] +} +``` + +## Further customization + +If further customization is needed, you can define an analyzer with additional `fingerprint` analyzer components: + +```json +PUT /custom_fingerprint_analyzer +{ + "settings": { + "analysis": { + "analyzer": { + "custom_fingerprint": { + "tokenizer": "standard", + "filter": [ + "lowercase", + "asciifolding", + "fingerprint" + ] + } + } + } + } +} +``` +{% include copy-curl.html %} From b248645d531044a47b4eef6d4acff399017090d1 Mon Sep 17 00:00:00 2001 From: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Date: Tue, 10 Dec 2024 14:22:08 -0500 Subject: [PATCH 64/69] Reorganize built-in analyzer section (#8922) * Reorganize built-in analyzer section Signed-off-by: Fanit Kolchina * Remove extra parentheses Signed-off-by: Fanit Kolchina --------- Signed-off-by: Fanit Kolchina --- _analyzers/custom-analyzer.md | 2 +- _analyzers/language-analyzers/index.md | 2 +- .../{ => supported-analyzers}/fingerprint.md | 3 ++- _analyzers/supported-analyzers/index.md | 18 +++++++++--------- .../{ => supported-analyzers}/keyword.md | 1 + .../{ => supported-analyzers}/pattern.md | 1 + .../supported-analyzers/phone-analyzers.md | 2 +- _analyzers/{ => supported-analyzers}/simple.md | 3 ++- .../{ => supported-analyzers}/standard.md | 3 ++- _analyzers/{ => supported-analyzers}/stop.md | 3 ++- .../{ => supported-analyzers}/whitespace.md | 3 ++- _analyzers/token-filters/index.md | 4 ++-- .../token-filters/word-delimiter-graph.md | 4 ++-- _analyzers/token-filters/word-delimiter.md | 4 ++-- _analyzers/tokenizers/index.md | 2 +- _analyzers/tokenizers/pattern.md | 4 ++-- _analyzers/tokenizers/simple-pattern-split.md | 2 +- _analyzers/tokenizers/whitespace.md | 2 +- 18 files changed, 35 insertions(+), 28 deletions(-) rename _analyzers/{ => supported-analyzers}/fingerprint.md (98%) rename _analyzers/{ => supported-analyzers}/keyword.md (98%) rename _analyzers/{ => supported-analyzers}/pattern.md (99%) rename _analyzers/{ => supported-analyzers}/simple.md (98%) rename _analyzers/{ => supported-analyzers}/standard.md (98%) rename _analyzers/{ => supported-analyzers}/stop.md (99%) rename _analyzers/{ => supported-analyzers}/whitespace.md (98%) diff --git a/_analyzers/custom-analyzer.md b/_analyzers/custom-analyzer.md index b808268f66..c456f3d826 100644 --- a/_analyzers/custom-analyzer.md +++ b/_analyzers/custom-analyzer.md @@ -1,7 +1,7 @@ --- layout: default title: Creating a custom analyzer -nav_order: 90 +nav_order: 40 parent: Analyzers --- diff --git a/_analyzers/language-analyzers/index.md b/_analyzers/language-analyzers/index.md index 89a4a42254..cc53c1cdac 100644 --- a/_analyzers/language-analyzers/index.md +++ b/_analyzers/language-analyzers/index.md @@ -1,7 +1,7 @@ --- layout: default title: Language analyzers -nav_order: 100 +nav_order: 140 parent: Analyzers has_children: true has_toc: true diff --git a/_analyzers/fingerprint.md b/_analyzers/supported-analyzers/fingerprint.md similarity index 98% rename from _analyzers/fingerprint.md rename to _analyzers/supported-analyzers/fingerprint.md index dd8027f037..267e16c039 100644 --- a/_analyzers/fingerprint.md +++ b/_analyzers/supported-analyzers/fingerprint.md @@ -1,7 +1,8 @@ --- layout: default title: Fingerprint analyzer -nav_order: 110 +parent: Analyzers +nav_order: 60 --- # Fingerprint analyzer diff --git a/_analyzers/supported-analyzers/index.md b/_analyzers/supported-analyzers/index.md index 43e41b8d6a..b54660478f 100644 --- a/_analyzers/supported-analyzers/index.md +++ b/_analyzers/supported-analyzers/index.md @@ -18,14 +18,14 @@ The following table lists the built-in analyzers that OpenSearch provides. The l Analyzer | Analysis performed | Analyzer output :--- | :--- | :--- -**Standard** (default) | - Parses strings into tokens at word boundaries
- Removes most punctuation
- Converts tokens to lowercase | [`it’s`, `fun`, `to`, `contribute`, `a`,`brand`, `new`, `pr`, `or`, `2`, `to`, `opensearch`] -**Simple** | - Parses strings into tokens on any non-letter character
- Removes non-letter characters
- Converts tokens to lowercase | [`it`, `s`, `fun`, `to`, `contribute`, `a`,`brand`, `new`, `pr`, `or`, `to`, `opensearch`] -**Whitespace** | - Parses strings into tokens on white space | [`It’s`, `fun`, `to`, `contribute`, `a`,`brand-new`, `PR`, `or`, `2`, `to`, `OpenSearch!`] -**Stop** | - Parses strings into tokens on any non-letter character
- Removes non-letter characters
- Removes stop words
- Converts tokens to lowercase | [`s`, `fun`, `contribute`, `brand`, `new`, `pr`, `opensearch`] -**Keyword** (no-op) | - Outputs the entire string unchanged | [`It’s fun to contribute a brand-new PR or 2 to OpenSearch!`] -**Pattern** | - Parses strings into tokens using regular expressions
- Supports converting strings to lowercase
- Supports removing stop words | [`it`, `s`, `fun`, `to`, `contribute`, `a`,`brand`, `new`, `pr`, `or`, `2`, `to`, `opensearch`] +[**Standard**]({{site.url}}{{site.baseurl}}/analyzers/supported-analyzers/standard/) (default) | - Parses strings into tokens at word boundaries
- Removes most punctuation
- Converts tokens to lowercase | [`it’s`, `fun`, `to`, `contribute`, `a`,`brand`, `new`, `pr`, `or`, `2`, `to`, `opensearch`] +[**Simple**]({{site.url}}{{site.baseurl}}/analyzers/supported-analyzers/simple/) | - Parses strings into tokens on any non-letter character
- Removes non-letter characters
- Converts tokens to lowercase | [`it`, `s`, `fun`, `to`, `contribute`, `a`,`brand`, `new`, `pr`, `or`, `to`, `opensearch`] +[**Whitespace**]({{site.url}}{{site.baseurl}}/analyzers/supported-analyzers/whitespace/) | - Parses strings into tokens on white space | [`It’s`, `fun`, `to`, `contribute`, `a`,`brand-new`, `PR`, `or`, `2`, `to`, `OpenSearch!`] +[**Stop**]({{site.url}}{{site.baseurl}}/analyzers/supported-analyzers/stop/) | - Parses strings into tokens on any non-letter character
- Removes non-letter characters
- Removes stop words
- Converts tokens to lowercase | [`s`, `fun`, `contribute`, `brand`, `new`, `pr`, `opensearch`] +[**Keyword**]({{site.url}}{{site.baseurl}}/analyzers/supported-analyzers/keyword/) (no-op) | - Outputs the entire string unchanged | [`It’s fun to contribute a brand-new PR or 2 to OpenSearch!`] +[**Pattern**]({{site.url}}{{site.baseurl}}/analyzers/supported-analyzers/pattern/)| - Parses strings into tokens using regular expressions
- Supports converting strings to lowercase
- Supports removing stop words | [`it`, `s`, `fun`, `to`, `contribute`, `a`,`brand`, `new`, `pr`, `or`, `2`, `to`, `opensearch`] [**Language**]({{site.url}}{{site.baseurl}}/analyzers/language-analyzers/index/) | Performs analysis specific to a certain language (for example, `english`). | [`fun`, `contribut`, `brand`, `new`, `pr`, `2`, `opensearch`] -**Fingerprint** | - Parses strings on any non-letter character
- Normalizes characters by converting them to ASCII
- Converts tokens to lowercase
- Sorts, deduplicates, and concatenates tokens into a single token
- Supports removing stop words | [`2 a brand contribute fun it's new opensearch or pr to`]
Note that the apostrophe was converted to its ASCII counterpart. +[**Fingerprint**]({{site.url}}{{site.baseurl}}/analyzers/supported-analyzers/fingerprint/) | - Parses strings on any non-letter character
- Normalizes characters by converting them to ASCII
- Converts tokens to lowercase
- Sorts, deduplicates, and concatenates tokens into a single token
- Supports removing stop words | [`2 a brand contribute fun it's new opensearch or pr to`]
Note that the apostrophe was converted to its ASCII counterpart. ## Language analyzers @@ -37,5 +37,5 @@ The following table lists the additional analyzers that OpenSearch supports. | Analyzer | Analysis performed | |:---------------|:---------------------------------------------------------------------------------------------------------| -| `phone` | An [index analyzer]({{site.url}}{{site.baseurl}}/analyzers/index-analyzers/) for parsing phone numbers. | -| `phone-search` | A [search analyzer]({{site.url}}{{site.baseurl}}/analyzers/search-analyzers/) for parsing phone numbers. | +| [`phone`]({{site.url}}{{site.baseurl}}/analyzers/supported-analyzers/phone-analyzers/#the-phone-analyzer) | An [index analyzer]({{site.url}}{{site.baseurl}}/analyzers/index-analyzers/) for parsing phone numbers. | +| [`phone-search`]({{site.url}}{{site.baseurl}}/analyzers/supported-analyzers/phone-analyzers/#the-phone-search-analyzer) | A [search analyzer]({{site.url}}{{site.baseurl}}/analyzers/search-analyzers/) for parsing phone numbers. | diff --git a/_analyzers/keyword.md b/_analyzers/supported-analyzers/keyword.md similarity index 98% rename from _analyzers/keyword.md rename to _analyzers/supported-analyzers/keyword.md index 3aec99d1d4..00c314d0c4 100644 --- a/_analyzers/keyword.md +++ b/_analyzers/supported-analyzers/keyword.md @@ -1,6 +1,7 @@ --- layout: default title: Keyword analyzer +parent: Analyzers nav_order: 80 --- diff --git a/_analyzers/pattern.md b/_analyzers/supported-analyzers/pattern.md similarity index 99% rename from _analyzers/pattern.md rename to _analyzers/supported-analyzers/pattern.md index 0d67999b82..bc3cb9a306 100644 --- a/_analyzers/pattern.md +++ b/_analyzers/supported-analyzers/pattern.md @@ -1,6 +1,7 @@ --- layout: default title: Pattern analyzer +parent: Analyzers nav_order: 90 --- diff --git a/_analyzers/supported-analyzers/phone-analyzers.md b/_analyzers/supported-analyzers/phone-analyzers.md index f24b7cf328..d94bfe192f 100644 --- a/_analyzers/supported-analyzers/phone-analyzers.md +++ b/_analyzers/supported-analyzers/phone-analyzers.md @@ -1,6 +1,6 @@ --- layout: default -title: Phone number +title: Phone number analyzers parent: Analyzers nav_order: 140 --- diff --git a/_analyzers/simple.md b/_analyzers/supported-analyzers/simple.md similarity index 98% rename from _analyzers/simple.md rename to _analyzers/supported-analyzers/simple.md index edfa7f58a6..29f8f9a533 100644 --- a/_analyzers/simple.md +++ b/_analyzers/supported-analyzers/simple.md @@ -1,7 +1,8 @@ --- layout: default title: Simple analyzer -nav_order: 50 +parent: Analyzers +nav_order: 100 --- # Simple analyzer diff --git a/_analyzers/standard.md b/_analyzers/supported-analyzers/standard.md similarity index 98% rename from _analyzers/standard.md rename to _analyzers/supported-analyzers/standard.md index e4a7a70fbc..d5c3650d5d 100644 --- a/_analyzers/standard.md +++ b/_analyzers/supported-analyzers/standard.md @@ -1,7 +1,8 @@ --- layout: default title: Standard analyzer -nav_order: 40 +parent: Analyzers +nav_order: 50 --- # Standard analyzer diff --git a/_analyzers/stop.md b/_analyzers/supported-analyzers/stop.md similarity index 99% rename from _analyzers/stop.md rename to _analyzers/supported-analyzers/stop.md index 68dc554473..df62c7fe58 100644 --- a/_analyzers/stop.md +++ b/_analyzers/supported-analyzers/stop.md @@ -1,7 +1,8 @@ --- layout: default title: Stop analyzer -nav_order: 70 +parent: Analyzers +nav_order: 110 --- # Stop analyzer diff --git a/_analyzers/whitespace.md b/_analyzers/supported-analyzers/whitespace.md similarity index 98% rename from _analyzers/whitespace.md rename to _analyzers/supported-analyzers/whitespace.md index 67fee61295..4691b4f733 100644 --- a/_analyzers/whitespace.md +++ b/_analyzers/supported-analyzers/whitespace.md @@ -1,7 +1,8 @@ --- layout: default title: Whitespace analyzer -nav_order: 60 +parent: Analyzers +nav_order: 120 --- # Whitespace analyzer diff --git a/_analyzers/token-filters/index.md b/_analyzers/token-filters/index.md index b06489c805..875e94db5a 100644 --- a/_analyzers/token-filters/index.md +++ b/_analyzers/token-filters/index.md @@ -63,5 +63,5 @@ Token filter | Underlying Lucene token filter| Description [`truncate`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/truncate/) | [TruncateTokenFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/TruncateTokenFilter.html) | Truncates tokens with lengths exceeding the specified character limit. [`unique`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/unique/) | N/A | Ensures that each token is unique by removing duplicate tokens from a stream. [`uppercase`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/uppercase/) | [UpperCaseFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/core/LowerCaseFilter.html) | Converts tokens to uppercase. -[`word_delimiter`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/word-delimiter/) | [WordDelimiterFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/WordDelimiterFilter.html) | Splits tokens at non-alphanumeric characters and performs normalization based on the specified rules. -[`word_delimiter_graph`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/word-delimiter-graph/) | [WordDelimiterGraphFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/WordDelimiterGraphFilter.html) | Splits tokens at non-alphanumeric characters and performs normalization based on the specified rules. Assigns a `positionLength` attribute to multi-position tokens. +[`word_delimiter`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/word-delimiter/) | [WordDelimiterFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/WordDelimiterFilter.html) | Splits tokens on non-alphanumeric characters and performs normalization based on the specified rules. +[`word_delimiter_graph`]({{site.url}}{{site.baseurl}}/analyzers/token-filters/word-delimiter-graph/) | [WordDelimiterGraphFilter](https://lucene.apache.org/core/9_10_0/analysis/common/org/apache/lucene/analysis/miscellaneous/WordDelimiterGraphFilter.html) | Splits tokens on non-alphanumeric characters and performs normalization based on the specified rules. Assigns a `positionLength` attribute to multi-position tokens. diff --git a/_analyzers/token-filters/word-delimiter-graph.md b/_analyzers/token-filters/word-delimiter-graph.md index ac734bebeb..b901f5a0e5 100644 --- a/_analyzers/token-filters/word-delimiter-graph.md +++ b/_analyzers/token-filters/word-delimiter-graph.md @@ -7,7 +7,7 @@ nav_order: 480 # Word delimiter graph token filter -The `word_delimiter_graph` token filter is used to split tokens at predefined characters and also offers optional token normalization based on customizable rules. +The `word_delimiter_graph` token filter is used to splits token on predefined characters and also offers optional token normalization based on customizable rules. The `word_delimiter_graph` filter is used to remove punctuation from complex identifiers like part numbers or product IDs. In such cases, it is best used with the `keyword` tokenizer. For hyphenated words, use the `synonym_graph` token filter instead of the `word_delimiter_graph` filter because users frequently search for these terms both with and without hyphens. {: .note} @@ -44,7 +44,7 @@ Parameter | Required/Optional | Data type | Description `split_on_case_change` | Optional | Boolean | Splits tokens where consecutive letters have different cases (one is lowercase and the other is uppercase). For example, `"OpenSearch"` becomes `[ Open, Search ]`. Default is `true`. `split_on_numerics` | Optional | Boolean | Splits tokens where there are consecutive letters and numbers. For example `"v8engine"` will become `[ v, 8, engine ]`. Default is `true`. `stem_english_possessive` | Optional | Boolean | Removes English possessive endings, such as `'s`. Default is `true`. -`type_table` | Optional | Array of strings | A custom map that specifies how to treat characters and whether to treat them as delimiters, which avoids unwanted splitting. For example, to treat a hyphen (`-`) as an alphanumeric character, specify `["- => ALPHA"]` so that words are not split at hyphens. Valid types are:
- `ALPHA`: alphabetical
- `ALPHANUM`: alphanumeric
- `DIGIT`: numeric
- `LOWER`: lowercase alphabetical
- `SUBWORD_DELIM`: non-alphanumeric delimiter
- `UPPER`: uppercase alphabetical +`type_table` | Optional | Array of strings | A custom map that specifies how to treat characters and whether to treat them as delimiters, which avoids unwanted splitting. For example, to treat a hyphen (`-`) as an alphanumeric character, specify `["- => ALPHA"]` so that words are not split on hyphens. Valid types are:
- `ALPHA`: alphabetical
- `ALPHANUM`: alphanumeric
- `DIGIT`: numeric
- `LOWER`: lowercase alphabetical
- `SUBWORD_DELIM`: non-alphanumeric delimiter
- `UPPER`: uppercase alphabetical `type_table_path` | Optional | String | Specifies a path (absolute or relative to the config directory) to a file containing a custom character map. The map specifies how to treat characters and whether to treat them as delimiters, which avoids unwanted splitting. For valid types, see `type_table`. ## Example diff --git a/_analyzers/token-filters/word-delimiter.md b/_analyzers/token-filters/word-delimiter.md index d820fae2a0..77a71f28fb 100644 --- a/_analyzers/token-filters/word-delimiter.md +++ b/_analyzers/token-filters/word-delimiter.md @@ -7,7 +7,7 @@ nav_order: 470 # Word delimiter token filter -The `word_delimiter` token filter is used to split tokens at predefined characters and also offers optional token normalization based on customizable rules. +The `word_delimiter` token filter is used to splits token on predefined characters and also offers optional token normalization based on customizable rules. We recommend using the `word_delimiter_graph` filter instead of the `word_delimiter` filter whenever possible because the `word_delimiter` filter sometimes produces invalid token graphs. For more information about the differences between the two filters, see [Differences between the `word_delimiter_graph` and `word_delimiter` filters]({{site.url}}{{site.baseurl}}/analyzers/token-filters/word-delimiter-graph/#differences-between-the-word_delimiter_graph-and-word_delimiter-filters). {: .important} @@ -45,7 +45,7 @@ Parameter | Required/Optional | Data type | Description `split_on_case_change` | Optional | Boolean | Splits tokens where consecutive letters have different cases (one is lowercase and the other is uppercase). For example, `"OpenSearch"` becomes `[ Open, Search ]`. Default is `true`. `split_on_numerics` | Optional | Boolean | Splits tokens where there are consecutive letters and numbers. For example `"v8engine"` will become `[ v, 8, engine ]`. Default is `true`. `stem_english_possessive` | Optional | Boolean | Removes English possessive endings, such as `'s`. Default is `true`. -`type_table` | Optional | Array of strings | A custom map that specifies how to treat characters and whether to treat them as delimiters, which avoids unwanted splitting. For example, to treat a hyphen (`-`) as an alphanumeric character, specify `["- => ALPHA"]` so that words are not split at hyphens. Valid types are:
- `ALPHA`: alphabetical
- `ALPHANUM`: alphanumeric
- `DIGIT`: numeric
- `LOWER`: lowercase alphabetical
- `SUBWORD_DELIM`: non-alphanumeric delimiter
- `UPPER`: uppercase alphabetical +`type_table` | Optional | Array of strings | A custom map that specifies how to treat characters and whether to treat them as delimiters, which avoids unwanted splitting. For example, to treat a hyphen (`-`) as an alphanumeric character, specify `["- => ALPHA"]` so that words are not split on hyphens. Valid types are:
- `ALPHA`: alphabetical
- `ALPHANUM`: alphanumeric
- `DIGIT`: numeric
- `LOWER`: lowercase alphabetical
- `SUBWORD_DELIM`: non-alphanumeric delimiter
- `UPPER`: uppercase alphabetical `type_table_path` | Optional | String | Specifies a path (absolute or relative to the config directory) to a file containing a custom character map. The map specifies how to treat characters and whether to treat them as delimiters, which avoids unwanted splitting. For valid types, see `type_table`. ## Example diff --git a/_analyzers/tokenizers/index.md b/_analyzers/tokenizers/index.md index 1f9e49c855..f5b5ff0f25 100644 --- a/_analyzers/tokenizers/index.md +++ b/_analyzers/tokenizers/index.md @@ -56,7 +56,7 @@ Tokenizer | Description | Example `keyword` | - No-op tokenizer
- Outputs the entire string unchanged
- Can be combined with token filters, like lowercase, to normalize terms | `My repo`
becomes
`My repo` `pattern` | - Uses a regular expression pattern to parse text into terms on a word separator or to capture matching text as terms
- Uses [Java regular expressions](https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html) | `https://opensearch.org/forum`
becomes
[`https`, `opensearch`, `org`, `forum`] because by default the tokenizer splits terms at word boundaries (`\W+`)
Can be configured with a regex pattern `simple_pattern` | - Uses a regular expression pattern to return matching text as terms
- Uses [Lucene regular expressions](https://lucene.apache.org/core/8_7_0/core/org/apache/lucene/util/automaton/RegExp.html)
- Faster than the `pattern` tokenizer because it uses a subset of the `pattern` tokenizer regular expressions | Returns an empty array by default
Must be configured with a pattern because the pattern defaults to an empty string -`simple_pattern_split` | - Uses a regular expression pattern to split the text at matches rather than returning the matches as terms
- Uses [Lucene regular expressions](https://lucene.apache.org/core/8_7_0/core/org/apache/lucene/util/automaton/RegExp.html)
- Faster than the `pattern` tokenizer because it uses a subset of the `pattern` tokenizer regular expressions | No-op by default
Must be configured with a pattern +`simple_pattern_split` | - Uses a regular expression pattern to split the text on matches rather than returning the matches as terms
- Uses [Lucene regular expressions](https://lucene.apache.org/core/8_7_0/core/org/apache/lucene/util/automaton/RegExp.html)
- Faster than the `pattern` tokenizer because it uses a subset of the `pattern` tokenizer regular expressions | No-op by default
Must be configured with a pattern `char_group` | - Parses on a set of configurable characters
- Faster than tokenizers that run regular expressions | No-op by default
Must be configured with a list of characters `path_hierarchy` | - Parses text on the path separator (by default, `/`) and returns a full path to each component in the tree hierarchy | `one/two/three`
becomes
[`one`, `one/two`, `one/two/three`] diff --git a/_analyzers/tokenizers/pattern.md b/_analyzers/tokenizers/pattern.md index f422d8c805..036dd9050f 100644 --- a/_analyzers/tokenizers/pattern.md +++ b/_analyzers/tokenizers/pattern.md @@ -11,7 +11,7 @@ The `pattern` tokenizer is a highly flexible tokenizer that allows you to split ## Example usage -The following example request creates a new index named `my_index` and configures an analyzer with a `pattern` tokenizer. The tokenizer splits text at `-`, `_`, or `.` characters: +The following example request creates a new index named `my_index` and configures an analyzer with a `pattern` tokenizer. The tokenizer splits text on `-`, `_`, or `.` characters: ```json PUT /my_index @@ -102,7 +102,7 @@ Parameter | Required/Optional | Data type | Description :--- | :--- | :--- | :--- `pattern` | Optional | String | The pattern used to split text into tokens, specified using a [Java regular expression](https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html). Default is `\W+`. `flags` | Optional | String | Configures pipe-separated [flags](https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html#field.summary) to apply to the regular expression, for example, `"CASE_INSENSITIVE|MULTILINE|DOTALL"`. -`group` | Optional | Integer | Specifies the capture group to be used as a token. Default is `-1` (split at a match). +`group` | Optional | Integer | Specifies the capture group to be used as a token. Default is `-1` (split on a match). ## Example using a group parameter diff --git a/_analyzers/tokenizers/simple-pattern-split.md b/_analyzers/tokenizers/simple-pattern-split.md index 1fd130082e..25367f25b5 100644 --- a/_analyzers/tokenizers/simple-pattern-split.md +++ b/_analyzers/tokenizers/simple-pattern-split.md @@ -13,7 +13,7 @@ The tokenizer uses the matched parts of the input text (based on the regular exp ## Example usage -The following example request creates a new index named `my_index` and configures an analyzer with a `simple_pattern_split` tokenizer. The tokenizer is configured to split text at hyphens: +The following example request creates a new index named `my_index` and configures an analyzer with a `simple_pattern_split` tokenizer. The tokenizer is configured to split text on hyphens: ```json PUT /my_index diff --git a/_analyzers/tokenizers/whitespace.md b/_analyzers/tokenizers/whitespace.md index 604eeeb6a0..fb168304a7 100644 --- a/_analyzers/tokenizers/whitespace.md +++ b/_analyzers/tokenizers/whitespace.md @@ -7,7 +7,7 @@ nav_order: 160 # Whitespace tokenizer -The `whitespace` tokenizer splits text at white space characters, such as spaces, tabs, and new lines. It treats each word separated by white space as a token and does not perform any additional analysis or normalization like lowercasing or punctuation removal. +The `whitespace` tokenizer splits text on white space characters, such as spaces, tabs, and new lines. It treats each word separated by white space as a token and does not perform any additional analysis or normalization like lowercasing or punctuation removal. ## Example usage From b1da213d859191a96d612a432215e97a33e82143 Mon Sep 17 00:00:00 2001 From: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> Date: Tue, 10 Dec 2024 13:28:45 -0600 Subject: [PATCH 65/69] Update transforms-apis.md (#8923) Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> --- _im-plugin/index-transforms/transforms-apis.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_im-plugin/index-transforms/transforms-apis.md b/_im-plugin/index-transforms/transforms-apis.md index 37d2c035b5..7e0803c38b 100644 --- a/_im-plugin/index-transforms/transforms-apis.md +++ b/_im-plugin/index-transforms/transforms-apis.md @@ -177,8 +177,8 @@ The update operation supports the following query parameters: Parameter | Description | Required :---| :--- | :--- -`seq_no` | Only perform the transform operation if the last operation that changed the transform job has the specified sequence number. | Yes -`primary_term` | Only perform the transform operation if the last operation that changed the transform job has the specified sequence term. | Yes +`if_seq_no` | Only perform the transform operation if the last operation that changed the transform job has the specified sequence number. | Yes +`if_primary_term` | Only perform the transform operation if the last operation that changed the transform job has the specified sequence term. | Yes ### Request body fields From befc0d29a85a2c57b137e9174944aab2458805d2 Mon Sep 17 00:00:00 2001 From: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> Date: Tue, 10 Dec 2024 15:59:23 -0600 Subject: [PATCH 66/69] Additional migration assistant feedback (#8895) * Fix migration assistant bugs Signed-off-by: Archer * Fix link Signed-off-by: Archer * Additional feedback Signed-off-by: Archer * Add additional feedback. Signed-off-by: Archer * Apply suggestions from code review Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Update index.md Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Update index.md Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Fix TOC. Signed-off-by: Archer * fix link Signed-off-by: Archer --------- Signed-off-by: Archer Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --- _includes/cards.html | 2 +- .../getting-started-data-migration.md | 6 ++++-- _migration-assistant/index.md | 4 ++-- .../migration-console/index.md | 1 + _migration-assistant/migration-phases/index.md | 11 +++++++++-- .../live-traffic-migration/index.md | 18 ++++++++++++++++++ ...witching-traffic-from-the-source-cluster.md | 5 ++++- .../using-traffic-replayer.md | 5 ++++- .../assessing-your-cluster-for-migration.md | 6 ++++-- .../planning-your-migration/index.md | 15 +++++++++++++++ .../verifying-migration-tools.md | 5 ++++- 11 files changed, 66 insertions(+), 12 deletions(-) rename _migration-assistant/{ => deploying-migration-assistant}/getting-started-data-migration.md (97%) create mode 100644 _migration-assistant/migration-phases/live-traffic-migration/index.md rename _migration-assistant/migration-phases/{ => live-traffic-migration}/switching-traffic-from-the-source-cluster.md (95%) rename _migration-assistant/migration-phases/{ => live-traffic-migration}/using-traffic-replayer.md (98%) rename _migration-assistant/migration-phases/{ => planning-your-migration}/assessing-your-cluster-for-migration.md (93%) create mode 100644 _migration-assistant/migration-phases/planning-your-migration/index.md rename _migration-assistant/migration-phases/{ => planning-your-migration}/verifying-migration-tools.md (98%) diff --git a/_includes/cards.html b/_includes/cards.html index 5ab37b8c27..3fa1809506 100644 --- a/_includes/cards.html +++ b/_includes/cards.html @@ -34,7 +34,7 @@

Migration Assistant

-

Migrate to OpenSearch from other platforms

+

Migrate to OpenSearch

diff --git a/_migration-assistant/getting-started-data-migration.md b/_migration-assistant/deploying-migration-assistant/getting-started-data-migration.md similarity index 97% rename from _migration-assistant/getting-started-data-migration.md rename to _migration-assistant/deploying-migration-assistant/getting-started-data-migration.md index 4110f29edf..f260a28701 100644 --- a/_migration-assistant/getting-started-data-migration.md +++ b/_migration-assistant/deploying-migration-assistant/getting-started-data-migration.md @@ -1,10 +1,12 @@ --- layout: default title: Getting started with data migration +parent: Deploying Migration Assistant nav_order: 10 redirect_from: - /upgrade-to/upgrade-to/ - /upgrade-to/snapshot-migrate/ + - /migration-assistant/getting-started-with-data-migration/ --- # Getting started with data migration @@ -207,8 +209,8 @@ Run the following command to access the migration console: {% include copy.html %} -`accessContainer.sh` is located in `/opensearch-migrations/deployment/cdk/opensearch-service-migration/` on the Bootstrap instance. To learn more, see [Accessing the migration console]. -`{: .note} +`accessContainer.sh` is located in `/opensearch-migrations/deployment/cdk/opensearch-service-migration/` on the Bootstrap instance. To learn more, see [Accessing the migration console]({{site.url}}{{site.baseurl}}/migration-assistant/migration-phases/migrating-metadata/). +{: .note} --- diff --git a/_migration-assistant/index.md b/_migration-assistant/index.md index f024fdb69c..8e8422f0c4 100644 --- a/_migration-assistant/index.md +++ b/_migration-assistant/index.md @@ -50,7 +50,7 @@ Acting as a traffic simulation tool, the Traffic Replayer replays recorded reque The Metadata migration tool integrated into the Migration CLI can be used independently to migrate cluster metadata, including index mappings, index configuration settings, templates, component templates, and aliases. -### reindex-from-snapshot +### Reindex-from-Snapshot `Reindex-from-Snapshot` (RFS) reindexes data from an existing snapshot. Workers on Amazon Elastic Container Service (Amazon ECS) coordinate the migration of documents from an existing snapshot, reindexing the documents in parallel to a target cluster. @@ -70,6 +70,6 @@ The design deployed in AWS is as follows: 2. An Application Load Balancer with capture proxies relays traffic to a source while replicating data to Amazon Managed Streaming for Apache Kafka (Amazon MSK). 3. Using the migration console, you can initiate metadata migration to establish indexes, templates, component templates, and aliases on the target cluster. 4. With continuous traffic capture in place, you can use a `reindex-from-snapshot` process to capture data from your current index. -4. Once `reindex-from-snapshot` is complete, captured traffic is replayed from Amazon MSK to the target cluster by the traffic replayer. +4. Once `Reindex-from-Snapshot` is complete, captured traffic is replayed from Amazon MSK to the target cluster by the traffic replayer. 5. Performance and behavior of traffic sent to the source and target clusters are compared by reviewing logs and metrics. 6. After confirming that the target cluster's functionality meets expectations, clients are redirected to the new target. diff --git a/_migration-assistant/migration-console/index.md b/_migration-assistant/migration-console/index.md index 3e08e72c5c..eab3001868 100644 --- a/_migration-assistant/migration-console/index.md +++ b/_migration-assistant/migration-console/index.md @@ -4,6 +4,7 @@ title: Migration console nav_order: 30 has_children: true permalink: /migration-console/ +has_toc: false redirect_from: - /migration-console/index/ --- diff --git a/_migration-assistant/migration-phases/index.md b/_migration-assistant/migration-phases/index.md index c3c6c14b07..63e4b574e5 100644 --- a/_migration-assistant/migration-phases/index.md +++ b/_migration-assistant/migration-phases/index.md @@ -3,14 +3,21 @@ layout: default title: Migration phases nav_order: 50 has_children: true +has_toc: false permalink: /migration-phases/ redirect_from: - /migration-phases/index/ --- -This page details how to conduct a migration with Migration Assistant. It encompasses a variety of scenarios including: +# Migration phases + +This page details how to conduct a migration with Migration Assistant. It shows you how to [plan for your migration]({{site.url}}{{site.baseurl}}/migration-assistant/migration-phases/planning-your-migration/index/) and encompasses a variety of migration scenarios, including: - [**Metadata migration**]({{site.url}}{{site.baseurl}}/migration-assistant/migration-phases/migrating-metadata/): Migrating cluster metadata, such as index settings, aliases, and templates. - [**Backfill migration**]({{site.url}}{{site.baseurl}}/migration-assistant/migration-phases/backfill/): Migrating existing or historical data from a source to a target cluster. -- **Live traffic migration**: Replicating live ongoing traffic from a source to a target cluster. +- [**Live traffic migration**]({{site.url}}{{site.baseurl}}/migration-assistant/migration-phases/using-traffic-replayer/): Replicating live ongoing traffic from [a source cluster]({{site.url}}{{site.baseurl}}/migration-assistant/migration-phases/switching-traffic-from-the-source-cluster/) to a target cluster. + + + + diff --git a/_migration-assistant/migration-phases/live-traffic-migration/index.md b/_migration-assistant/migration-phases/live-traffic-migration/index.md new file mode 100644 index 0000000000..f9e3d9f71f --- /dev/null +++ b/_migration-assistant/migration-phases/live-traffic-migration/index.md @@ -0,0 +1,18 @@ +--- +layout: default +title: Live traffic migration +nav_order: 99 +parent: Migration phases +has_toc: false +has_children: true +--- + +# Live traffic migration + +Live traffic migration intercepts HTTP requests to a source cluster and stores them in a durable stream before forwarding them to the source cluster. The stored requests are then duplicated and replayed to the target cluster. This process synchronizes the source and target clusters while highlighting behavioral and performance differences between them. Kafka is used to manage the data flow and reconstruct HTTP requests. You can monitor the replication process through CloudWatch metrics and the [migration console]({{site.url}}{{site.baseurl}}/migration-console/), which provides results in JSON format for analysis. + +To start with live traffic migration, use the following steps: + +1. [Using Traffic Replayer]({{site.url}}{{site.baseurl}}/migration-assistant/migration-phases/using-traffic-replayer/) +2. [Switching traffic from the source cluster]({{site.url}}{{site.baseurl}}/migration-assistant/migration-phases/switching-traffic-from-the-source-cluster/) + diff --git a/_migration-assistant/migration-phases/switching-traffic-from-the-source-cluster.md b/_migration-assistant/migration-phases/live-traffic-migration/switching-traffic-from-the-source-cluster.md similarity index 95% rename from _migration-assistant/migration-phases/switching-traffic-from-the-source-cluster.md rename to _migration-assistant/migration-phases/live-traffic-migration/switching-traffic-from-the-source-cluster.md index c43580eef9..e9e477654a 100644 --- a/_migration-assistant/migration-phases/switching-traffic-from-the-source-cluster.md +++ b/_migration-assistant/migration-phases/live-traffic-migration/switching-traffic-from-the-source-cluster.md @@ -2,7 +2,10 @@ layout: default title: Switching traffic from the source cluster nav_order: 110 -parent: Migration phases +grand_parent: Migration phases +parent: Live traffic migration +redirect_from: + - /migration-assistant/migration-phases/switching-traffic-from-the-source-cluster/ --- # Switching traffic from the source cluster diff --git a/_migration-assistant/migration-phases/using-traffic-replayer.md b/_migration-assistant/migration-phases/live-traffic-migration/using-traffic-replayer.md similarity index 98% rename from _migration-assistant/migration-phases/using-traffic-replayer.md rename to _migration-assistant/migration-phases/live-traffic-migration/using-traffic-replayer.md index 5b7af3c3f7..05c734d7be 100644 --- a/_migration-assistant/migration-phases/using-traffic-replayer.md +++ b/_migration-assistant/migration-phases/live-traffic-migration/using-traffic-replayer.md @@ -2,7 +2,10 @@ layout: default title: Using Traffic Replayer nav_order: 100 -parent: Migration phases +grand_parent: Migration phases +parent: Live traffic migration +redirect_from: + - /migration-assistant/migration-phases/using-traffic-replayer/ --- # Using Traffic Replayer diff --git a/_migration-assistant/migration-phases/assessing-your-cluster-for-migration.md b/_migration-assistant/migration-phases/planning-your-migration/assessing-your-cluster-for-migration.md similarity index 93% rename from _migration-assistant/migration-phases/assessing-your-cluster-for-migration.md rename to _migration-assistant/migration-phases/planning-your-migration/assessing-your-cluster-for-migration.md index 5ded49eb59..23bceb7114 100644 --- a/_migration-assistant/migration-phases/assessing-your-cluster-for-migration.md +++ b/_migration-assistant/migration-phases/planning-your-migration/assessing-your-cluster-for-migration.md @@ -2,12 +2,14 @@ layout: default title: Assessing your cluster for migration nav_order: 60 -parent: Migration phases +parent: Planning your migration +grand_parent: Migration phases +redirect_from: + - /migration-assistant/migration-phases/assessing-your-cluster-for-migration/ --- # Assessing your cluster for migration - The goal of the Migration Assistant is to streamline the process of migrating from one location or version of Elasticsearch/OpenSearch to another. However, completing a migration sometimes requires resolving client compatibility issues before they can communicate directly with the target cluster. ## Understanding breaking changes diff --git a/_migration-assistant/migration-phases/planning-your-migration/index.md b/_migration-assistant/migration-phases/planning-your-migration/index.md new file mode 100644 index 0000000000..4f0d264e93 --- /dev/null +++ b/_migration-assistant/migration-phases/planning-your-migration/index.md @@ -0,0 +1,15 @@ +--- +layout: default +title: Planning your migration +nav_order: 59 +parent: Migration phases +has_toc: false +has_children: true +--- + +# Planning your migration + +This section describes how to plan for your migration to OpenSearch by: + +- [Assessing your current cluster for migration]({{site.url}}{{site.baseurl}}/migration-assistant/migration-phases/planning-your-migration/assessing-your-cluster-for-migration/). +- [Verifying that you have the tools for migration]({{site.url}}{{site.baseurl}}/migration-assistant/migration-phases/planning-your-migration/verifying-migration-tools/). \ No newline at end of file diff --git a/_migration-assistant/migration-phases/verifying-migration-tools.md b/_migration-assistant/migration-phases/planning-your-migration/verifying-migration-tools.md similarity index 98% rename from _migration-assistant/migration-phases/verifying-migration-tools.md rename to _migration-assistant/migration-phases/planning-your-migration/verifying-migration-tools.md index 77df2b4280..aa2c5466cd 100644 --- a/_migration-assistant/migration-phases/verifying-migration-tools.md +++ b/_migration-assistant/migration-phases/planning-your-migration/verifying-migration-tools.md @@ -2,7 +2,10 @@ layout: default title: Verifying migration tools nav_order: 70 -parent: Migration phases +parent: Planning your migration +grand_parent: Migration phases +redirect_from: + - /migration-assistant/migration-phases/verifying-migration-tools/ --- # Verifying migration tools From f188c1894155ac8e6d30d16ee052706709e9dbb5 Mon Sep 17 00:00:00 2001 From: Stavros Macrakis <134456002+smacrakis@users.noreply.github.com> Date: Wed, 11 Dec 2024 12:12:34 -0500 Subject: [PATCH 67/69] Improve wording (#8929) "comprises" isn't quite right Signed-off-by: Stavros Macrakis <134456002+smacrakis@users.noreply.github.com> --- _install-and-configure/plugins.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_install-and-configure/plugins.md b/_install-and-configure/plugins.md index e96b29e822..055d451081 100644 --- a/_install-and-configure/plugins.md +++ b/_install-and-configure/plugins.md @@ -10,9 +10,9 @@ redirect_from: # Installing plugins -OpenSearch comprises of a number of plugins that add features and capabilities to the core platform. The plugins available to you are dependent on how OpenSearch was installed and which plugins were subsequently added or removed. For example, the minimal distribution of OpenSearch enables only core functionality, such as indexing and search. Using the minimal distribution of OpenSearch is beneficial when you are working in a testing environment, have custom plugins, or are intending to integrate OpenSearch with other services. +OpenSearch includes a number of plugins that add features and capabilities to the core platform. The plugins available to you are dependent on how OpenSearch was installed and which plugins were subsequently added or removed. For example, the minimal distribution of OpenSearch enables only core functionality, such as indexing and search. Using the minimal distribution of OpenSearch is beneficial when you are working in a testing environment, have custom plugins, or are intending to integrate OpenSearch with other services. -The standard distribution of OpenSearch has much more functionality included. You can choose to add additional plugins or remove any of the plugins you don't need. +The standard distribution of OpenSearch includes many more plugins offering much more functionality. You can choose to add additional plugins or remove any of the plugins you don't need. For a list of the available plugins, see [Available plugins](#available-plugins). From 8ee4746372711cc520711ce5e8aa266fee1c9d7c Mon Sep 17 00:00:00 2001 From: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Date: Wed, 11 Dec 2024 17:01:42 -0500 Subject: [PATCH 68/69] Add 1.3.20 to version history (#8927) * Add 1.3.20 to version history Signed-off-by: Fanit Kolchina * Revise date and description Signed-off-by: Fanit Kolchina --------- Signed-off-by: Fanit Kolchina --- _about/version-history.md | 1 + 1 file changed, 1 insertion(+) diff --git a/_about/version-history.md b/_about/version-history.md index bfbd8e9f55..d1cf98c178 100644 --- a/_about/version-history.md +++ b/_about/version-history.md @@ -34,6 +34,7 @@ OpenSearch version | Release highlights | Release date [2.0.1](https://github.com/opensearch-project/opensearch-build/blob/main/release-notes/opensearch-release-notes-2.0.1.md) | Includes bug fixes and maintenance updates for Alerting and Anomaly Detection. | 16 June 2022 [2.0.0](https://github.com/opensearch-project/opensearch-build/blob/main/release-notes/opensearch-release-notes-2.0.0.md) | Includes document-level monitors for alerting, OpenSearch Notifications plugins, and Geo Map Tiles in OpenSearch Dashboards. Also adds support for Lucene 9 and bug fixes for all OpenSearch plugins. For a full list of release highlights, see the Release Notes. | 26 May 2022 [2.0.0-rc1](https://github.com/opensearch-project/opensearch-build/blob/main/release-notes/opensearch-release-notes-2.0.0-rc1.md) | The Release Candidate for 2.0.0. This version allows you to preview the upcoming 2.0.0 release before the GA release. The preview release adds document-level alerting, support for Lucene 9, and the ability to use term lookup queries in document level security. | 03 May 2022 +[1.3.20](https://github.com/opensearch-project/opensearch-build/blob/main/release-notes/opensearch-release-notes-1.3.20.md) | Includes enhancements to Anomaly Detection Dashboards, bug fixes for Alerting and Dashboards Reports, and maintenance updates for several OpenSearch components. | 11 December 2024 [1.3.19](https://github.com/opensearch-project/opensearch-build/blob/main/release-notes/opensearch-release-notes-1.3.19.md) | Includes bug fixes and maintenance updates for OpenSearch security, OpenSearch security Dashboards, and anomaly detection. | 27 August 2024 [1.3.18](https://github.com/opensearch-project/opensearch-build/blob/main/release-notes/opensearch-release-notes-1.3.18.md) | Includes maintenance updates for OpenSearch security. | 16 July 2024 [1.3.17](https://github.com/opensearch-project/opensearch-build/blob/main/release-notes/opensearch-release-notes-1.3.17.md) | Includes maintenance updates for OpenSearch security and OpenSearch Dashboards security. | 06 June 2024 From 23729b7dd12c819b643fe62b02fce5f1fed40520 Mon Sep 17 00:00:00 2001 From: Andriy Redko Date: Wed, 11 Dec 2024 17:02:15 -0500 Subject: [PATCH 69/69] Update JDK version for 1.3 distributions (#8611) Signed-off-by: Andriy Redko --- _install-and-configure/install-opensearch/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_install-and-configure/install-opensearch/index.md b/_install-and-configure/install-opensearch/index.md index 94c259667a..bfaf9897d6 100644 --- a/_install-and-configure/install-opensearch/index.md +++ b/_install-and-configure/install-opensearch/index.md @@ -29,7 +29,7 @@ The OpenSearch distribution for Linux ships with a compatible [Adoptium JDK](htt OpenSearch Version | Compatible Java Versions | Bundled Java Version :---------- | :-------- | :----------- 1.0--1.2.x | 11, 15 | 15.0.1+9 -1.3.x | 8, 11, 14 | 11.0.24+8 +1.3.x | 8, 11, 14 | 11.0.25+9 2.0.0--2.11.x | 11, 17 | 17.0.2+8 2.12.0+ | 11, 17, 21 | 21.0.5+11