1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

OpenSearch Serverlessのインデックス状態を確実に取得してBedrockエラーを回避する方法

1
Last updated at Posted at 2025-09-12

はじめに

Amazon BedrockのKnowledge BaseとOpenSearch Serverlessを連携したRAG(Retrieval-Augmented Generation)システムを構築する際、「no such index」エラーに遭遇したことはありませんか?

このエラーの根本原因は、OpenSearch Serverlessのインデックスが実際に使用可能になったかどうかを正確に判定できていないことにあります。本記事では、OpenSearch Serverlessのインデックス状態を確実に取得・監視する方法を中心に、実装例とともに解説します。

問題の概要

発生するエラー

no such index [bedrock-knowledge-base-default-index]

エラーが発生する理由

  1. インデックス状態の誤判定: コレクションがACTIVEでもインデックスが使用可能とは限らない
  2. 状態確認方法の不備: 単純な存在確認だけでは実際の使用可能性を判定できない
  3. OpenSearch Serverless特有の挙動: 通常のOpenSearchとは異なる状態管理が必要

解決策の実装

1. インデックス状態監視の核心部分

問題解決の核心は、OpenSearch Serverlessのインデックスが実際に使用可能になったかを多段階で確認することです。単純な存在確認では不十分で、以下の段階的チェックが必要です:

// インデックス状態の段階的チェック
async waitForReady(client: Client, indexName: string): Promise<void> {
  const maxRetries = 12
  const delayMs = 15000
  let basicCheckPassed = false

  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    // 【ステップ1】インデックス存在確認
    const indexExists = await this.checkIndexExists(client, indexName, attempt)
    if (!indexExists) {
      await this.waitBeforeNextAttempt(attempt, maxRetries, delayMs)
      continue
    }

    // 【ステップ2】基本ステータス確認(初回のみ)
    if (!basicCheckPassed) {
      const basicCheckResult = await this.checkIndexBasicStatus(client, indexName)
      if (!basicCheckResult) {
        await this.waitBeforeNextAttempt(attempt, maxRetries, delayMs)
        continue
      }
      basicCheckPassed = true
      console.log('Basic validation passed, switching to operation tests only')
    }

    // 【ステップ3】実際の操作テスト
    const isOperational = await this.testIndexOperations(client, indexName)
    if (isOperational) {
      console.log(`Index ${indexName} is ready after ${attempt} attempts`)
      return
    }

    await this.waitBeforeNextAttempt(attempt, maxRetries, delayMs)
  }

  console.warn(`Index may not be fully ready after ${maxRetries} attempts`)
}

2. ステップ1: インデックス存在確認の実装

最初のステップは、インデックスが物理的に存在するかの確認です:

private async checkIndexExists(
  client: Client,
  indexName: string,
  attempt: number,
): Promise<boolean> {
  try {
    const existsResponse = await client.indices.exists({ index: indexName })
    if (existsResponse.body === true) {
      console.log(`Index ${indexName} exists, performing additional checks...`)
      return true
    } else {
      console.log(`Attempt ${attempt}: Index ${indexName} does not exist yet`)
      return false
    }
  } catch (error: any) {
    console.log(`Attempt ${attempt}: Error checking index existence: ${error.message}`)
    return false
  }
}

3. ステップ2: 基本ステータス確認の実装

ここが最も重要な部分です。_cat/indices APIを使用してインデックスの詳細な状態を確認します:

private async checkIndexBasicStatus(
  client: Client,
  indexName: string,
): Promise<boolean> {
  try {
    // _cat/indices APIでインデックスの詳細情報を取得
    const catResponse = await client.cat.indices({
      index: indexName,
      format: 'json', // JSON形式で取得
    })

    if (!catResponse.body || !Array.isArray(catResponse.body) || catResponse.body.length === 0) {
      console.log(`Index not found in _cat/indices yet`)
      return false
    }

    const indexInfo = catResponse.body[0]
    console.log(`_cat/indices response:`, JSON.stringify(indexInfo, null, 2))

    const { status, health } = indexInfo
    console.log(`Index status: ${status}, health: ${health}`)

    // ステータスとヘルス状態の確認
    const isStatusValid = status === 'OPEN' || status === 'open'
    const isHealthValid = health === 'green' || health === 'yellow' || health === ''
    const hasUuid = indexInfo.uuid && indexInfo.uuid !== ''

    if (isStatusValid && isHealthValid && hasUuid) {
      console.log(`Basic validation passed - status: ${status}, health: ${health}, uuid: ${indexInfo.uuid}`)
      return true
    } else {
      console.log(`Basic validation failed - status: ${status} (valid: ${isStatusValid}), health: ${health} (valid: ${isHealthValid}), uuid: ${indexInfo.uuid} (valid: ${hasUuid})`)
      return false
    }
  } catch (catError: any) {
    console.log(`_cat/indices check failed: ${catError.message}`)
    return false
  }
}

4. ステップ3: 実際の操作テストの実装

最終的に、インデックスが実際に操作可能かをテストします:

private async testIndexOperations(
  client: Client,
  indexName: string,
): Promise<boolean> {
  try {
    // マッピング取得テスト
    await client.indices.getMapping({ index: indexName })
    console.log(`Mapping check passed`)

    // 検索テスト(実際にクエリを実行)
    await client.search({
      index: indexName,
      body: { query: { match_all: {} }, size: 0 },
    })
    console.log(`Search test passed - index is fully operational`)
    return true
  } catch (testError: any) {
    console.log(`Operation test failed: ${testError.message}`)
    return false
  }
}

5. 完全なインデックス作成と監視の実装

以下が、Bedrockが期待するインデックスを作成し、確実に使用可能になるまで待機する完全な実装です:

async createBedrockDefaultIndex(collectionArn: string): Promise<void> {
  const indexName = 'bedrock-knowledge-base-default-index'

  try {
    const endpoint = this.createEndpoint(collectionArn)
    const client = await this.createClient({ endpoint })

    // インデックス作成
    const indexMapping = {
      settings: { index: { knn: true } },
      mappings: {
        properties: {
          'bedrock-knowledge-base-default-vector': {
            type: 'knn_vector',
            dimension: 1024,
            method: {
              name: 'hnsw',
              space_type: 'l2',
              engine: 'faiss',
              parameters: { ef_construction: 512, m: 16 },
            },
          },
          AMAZON_BEDROCK_TEXT_CHUNK: { type: 'text' },
          AMAZON_BEDROCK_METADATA: { type: 'text' },
        },
      },
    }

    await client.indices.create({
      index: indexName,
      body: indexMapping,
    })

    // 【重要】インデックスの準備完了まで確実に待機
    await this.waitForReady(client, indexName)
  } catch (error) {
    if (error.body?.error?.type === 'resource_already_exists_exception') {
      console.log('Bedrock default index already exists')
      return
    }
    throw error
  }
}

6. 実際の使用例とログ出力

実際にこの実装を使用した際のログ出力例:

Creating OpenSearch Serverless Collection: sample-collection-tenant123
Collection is now active: sample-collection-tenant123
Waiting for endpoint to be ready...
Creating Bedrock default index: bedrock-knowledge-base-default-index
OpenSearch endpoint: https://sample-endpoint.ap-northeast-1.aoss.amazonaws.com
Waiting for index bedrock-knowledge-base-default-index to be ready...
Will check up to 12 times with 15000ms intervals
Attempt 1: Index bedrock-knowledge-base-default-index does not exist yet
Waiting 15000ms before next attempt...
Index bedrock-knowledge-base-default-index exists, performing additional checks...
_cat/indices response: {
  "health": "green",
  "status": "open",
  "index": "bedrock-knowledge-base-default-index",
  "uuid": "sample-uuid-123456",
  "pri": "1",
  "rep": "0",
  "docs.count": "0",
  "docs.deleted": "0",
  "store.size": "208b",
  "pri.store.size": "208b"
}
Index status: open, health: green
Basic validation passed - status: open, health: green, uuid: sample-uuid-123456
Basic validation passed, switching to operation tests only
Mapping check passed
Search test passed - index is fully operational
Index bedrock-knowledge-base-default-index is ready after 3 attempts (45 seconds)

追加で知っておくべき重要な実装詳細

7. OpenSearchクライアントの認証設定

OpenSearch Serverlessへの接続では、適切な認証設定が必要です:

import { Client } from '@opensearch-project/opensearch'
import { AwsSigv4Signer } from '@opensearch-project/opensearch/aws'
import { defaultProvider } from '@aws-sdk/credential-provider-node'

export class OpenSearchClientFactory {
  static async createClient(config: { endpoint: string }): Promise<Client> {
    // 認証情報を事前に取得してテスト
    const credentials = await defaultProvider()()
    if (!credentials.accessKeyId || !credentials.secretAccessKey) {
      throw new Error('Invalid AWS credentials')
    }

    const client = new Client({
      ...AwsSigv4Signer({
        region: 'ap-northeast-1',
        service: 'aoss', // OpenSearch Serverless用
        getCredentials: async () => ({
          accessKeyId: credentials.accessKeyId,
          secretAccessKey: credentials.secretAccessKey,
          sessionToken: credentials.sessionToken,
        }),
      }),
      node: config.endpoint,
      ssl: { rejectUnauthorized: true },
      requestTimeout: 30000,
      pingTimeout: 3000,
    })

    return client
  }

  // コレクションARNからエンドポイントを生成
  static createEndpoint(collectionArn: string): string {
    const collectionId = collectionArn.split('/').pop()
    return `https://${collectionId}.ap-northeast-1.aoss.amazonaws.com`
  }
}

8. 必要なIAMポリシーとアクセス権限

OpenSearch Serverlessでは、以下のポリシーが必要です:

// データアクセスポリシーの例
const dataAccessPolicy = {
  Rules: [
    {
      Resource: [`collection/sample-collection`],
      Permission: [
        'aoss:CreateCollectionItems',
        'aoss:DescribeCollectionItems',
        'aoss:UpdateCollectionItems',
        'aoss:DeleteCollectionItems',
      ],
      ResourceType: 'collection',
    },
    {
      Resource: [`index/sample-collection/*`],
      Permission: [
        'aoss:CreateIndex',
        'aoss:DescribeIndex',
        'aoss:ReadDocument',
        'aoss:WriteDocument',
        'aoss:UpdateIndex',
        'aoss:DeleteIndex',
      ],
      ResourceType: 'index',
    },
  ],
  Principal: [bedrockRoleArn, lambdaRoleArn],
}

重要なポイントとベストプラクティス

1. インデックス状態確認の3段階アプローチ

従来の問題: 単純なindices.exists()だけでは不十分
解決策: 以下の3段階で確実にチェック

  1. 存在確認: client.indices.exists() でインデックスの物理的存在を確認
  2. 状態確認: client.cat.indices() でステータス・ヘルス・UUIDを確認
  3. 操作確認: 実際にマッピング取得・検索クエリを実行してテスト

2. _cat/indices APIの活用

重要: OpenSearch Serverlessでは、_cat/indicesが最も信頼できる状態確認方法

// 必須の確認項目
const { status, health, uuid } = indexInfo
const isReady = status === 'open' && health !== 'red' && uuid && uuid !== ''

3. 適切な待機間隔とリトライ回数

const maxRetries = 12 // 最大12回
const delayMs = 15000 // 15秒間隔
// 合計最大3分間の待機

4. エラーハンドリングのパターン

// 既存インデックスの処理
if (error.body?.error?.type === 'resource_already_exists_exception') {
  console.log('Index already exists, continuing...')
  return
}

// 一時的な接続エラーの処理
if (error.name === 'ConnectionError' || error.code === 'ECONNRESET') {
  console.log('Temporary connection error, retrying...')
  continue
}

まとめ

「no such index」エラーを回避するための要点:

  1. 3段階の状態確認: 存在確認 → _cat/indicesによる詳細確認 → 実際の操作テスト
  2. _cat/indices APIの活用: OpenSearch Serverlessで最も信頼できる状態確認方法
  3. 適切な待機戦略: 15秒間隔で最大12回(3分間)のリトライ
  4. 実際の操作テスト: マッピング取得と検索クエリによる動作確認
  5. 段階的チェック: 基本確認通過後は操作テストのみに切り替え

特に重要なのは、インデックスの「存在」と「使用可能性」は別物であることを理解し、_cat/indicesAPIを使って詳細な状態を確認することです。

この実装により、OpenSearch Serverlessのインデックス状態を確実に監視し、Bedrockとの連携エラーを防ぐことができます。従来の方法では見つからなかった、実用的なインデックス状態取得方法として活用していただければと思います。

トラブルシューティング

よくある問題と解決方法

1. 「Connection timeout」エラー

原因: ネットワークポリシーまたはエンドポイントの問題
解決: ネットワークポリシーでパブリックアクセスが許可されているか確認

2. 「Access denied」エラー

原因: データアクセスポリシーの設定不備
解決: BedrockロールとLambdaロールが適切にポリシーに含まれているか確認

3. インデックス作成後も「no such index」エラー

原因: インデックスの準備完了前にBedrockが実行された
解決: 本記事の3段階チェックを実装して確実に待機

4. 「Invalid credentials」エラー

原因: AWS認証情報の問題
解決: Lambda実行ロールに適切なOpenSearch Serverlessアクセス権限を付与

参考資料


この記事が、同様の問題に直面している方の助けになれば幸いです。質問やフィードバックがあれば、コメントでお聞かせください!

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?