博客

正确使用 JavaScript 的 Elasticsearch,第二部分

了解生产环境最佳实践,并学习如何在 Serverless 环境中运行 Elasticsearch Node.js 客户端,以减少代码错误。

这是 Elasticsearch in JavaScript 系列的第二部分。在 第一部分 中 , 我们学习了如何正确设置环境、配置 Node.js 客户端、索引数据和搜索。在第二部分中,我们将学习如何实施生产最佳实践,并在无服务器环境中运行 ElasticsearchNode.js客户端。

我们将审查

您可以 在这里查看示例的源代码

生产最佳实践

Elasticsearch 中的错误处理

Node.js 中 Elasticsearch 客户端的一个有用功能是,它为 Elasticsearch 中可能出现的错误提供了对象,因此您可以用不同的方式验证和处理这些错误。

查看全部内容,请执行此操作:

const { errors } = require('@elastic/elasticsearch')
console.log(errors)

让我们回到搜索示例,处理一些可能出现的错误:

app.get("/search/lexic", async (req, res) => {
 ....
  } catch (error) {
    if (error instanceof errors.ResponseError) {
      let errorMessage =
        "Response error!, query malformed or server down, contact the administrator!";

      if (error.body.error.type === "parsing_exception") {
        errorMessage = "Query malformed, make sure mappings are set correctly";
      }

      res.status(error.meta.statusCode).json({
        erroStatus: error.meta.statusCode,
        success: false,
        results: null,
        error: errorMessage,
      });
    }

    res.status(500).json({
      success: false,
      results: null,
      error: error.message,
    });
  }
});

ResponseError 尤其是当答案为4xx5xx 时,即表示请求不正确或服务器不可用。

我们可以通过生成错误查询来测试这类错误,比如尝试在文本类型字段上进行术语查询:

默认错误:

 {
    "success": false,
    "results": null,
    "error": "parsing_exception\n\tRoot causes:\n\t\tparsing_exception: [terms] query does not support [visit_details]"
}

定制错误:

{
    "erroStatus": 400,
    "success": false,
    "results": null,
    "error": "Response error!, query malformed or server down; contact the administrator!"
}

我们还可以以某种方式捕捉和处理每种类型的错误。例如,我们可以在TimeoutError 中添加重试逻辑。

app.get("/search/semantic", async (req, res) => {
    try {
  ...
  } catch (error) {
    if (error instanceof errors.TimeoutError) {


     // Retry logic...

      res.status(error.meta.statusCode).json({
        erroStatus: error.meta.statusCode,
        success: false,
        results: null,
        error:
          "The request took more than 10s after 3 retries. Try again later.",
      });
    }
  }
});

测试

测试是保证应用程序稳定性的关键。为了以一种与 Elasticsearch 隔离的方式测试代码,我们可以在创建集群时使用elasticsearch-js-mock库。

通过该库,我们可以实例化一个与真实客户端非常相似的客户端,但只需将客户端的 HTTP 层替换为模拟层,其他部分与原始客户端保持一致,就能满足我们的配置要求。

我们将安装 mocks 库和用于自动测试的AVA

npm install @elastic/elasticsearch-mock

npm install --save-dev ava

我们将配置package.json 文件以运行测试。确保它看起来是这样的:

"type": "module",
	"scripts": {
		"test": "ava"
	},
	"devDependencies": {
		"ava": "^5.0.0"
	}

现在,让我们创建test.js 文件并安装我们的模拟客户端:

const { Client } = require('@elastic/elasticsearch')
const Mock = require('@elastic/elasticsearch-mock')

const mock = new Mock()
const client = new Client({
  node: 'http://localhost:9200',
  Connection: mock.getConnection()
})

现在,为语义搜索添加一个模拟:

function createSemanticSearchMock(query, indexName) {
  mock.add(
    {
      method: "POST",
      path: `/${indexName}/_search`,
      body: {
        query: {
          semantic: {
            field: "semantic_field",
            query: query,
          },
        },
      },
    },
    () => {
      return {
        hits: {
          total: { value: 2, relation: "eq" },
          hits: [
            {
              _id: "1",
              _score: 0.9,
              _source: {
                owner_name: "Alice Johnson",
                pet_name: "Buddy",
                species: "Dog",
                breed: "Golden Retriever",
                vaccination_history: ["Rabies", "Parvovirus", "Distemper"],
                visit_details:
                  "Annual check-up and nail trimming. Healthy and active.",
              },
            },
            {
              _id: "2",
              _score: 0.7,
              _source: {
                owner_name: "Daniel Kim",
                pet_name: "Mochi",
                species: "Rabbit",
                breed: "Mixed",
                vaccination_history: [],
                visit_details:
                  "Nail trimming and general health check. No issues.",
              },
            },
          ],
        },
      };
    }
  );
}

现在我们可以为代码创建一个测试,确保 Elasticsearch 部分始终返回相同的结果:

import test from 'ava';

test("performSemanticSearch must return formatted results correctly", async (t) => {
  const indexName = "vet-visits";
  const query = "Which pets had nail trimming?";

  createSemanticSearchMock(query, indexName);

  async function performSemanticSearch(esClient, q, indexName = "vet-visits") {
    try {
      const result = await esClient.search({
        index: indexName,
        body: {
          query: {
            semantic: {
              field: "semantic_field",
              query: q,
            },
          },
        },
      });

      return {
        success: true,
        results: result.hits.hits,
      };
    } catch (error) {
      if (error instanceof errors.TimeoutError) {
        return {
          success: false,
          results: null,
          error: error.body.error.reason,
        };
      }

      return {
        success: false,
        results: null,
        error: error.message,
      };
    }
  }

  const result = await performSemanticSearch(esClient, query, indexName);

  t.true(result.success, "The search must be successful");
  t.true(Array.isArray(result.results), "The results must be an array");

  if (result.results.length > 0) {
    t.true(
      "_source" in result.results[0],
      "Each result must have a _source property"
    );
    t.true(
      "pet_name" in result.results[0]._source,
      "Results must include the pet_name field"
    );
    t.true(
      "visit_details" in result.results[0]._source,
      "Results must include the visit_details field"
    );
  }
});

让我们进行测试。

npm run test

完成!从现在起,我们就可以测试我们的应用程序,100% 专注于代码而不是外部因素。

无服务器环境

如何在 Elastic Serverless 上运行客户端

我们介绍了在云端或内部运行 Elasticsearch 的情况;不过,Node.js 客户端也支持与Elastic Cloud Serverless 的连接。

Elastic Cloud Serverless 允许您创建一个项目,在这个项目中,您无需担心基础设施问题,因为 Elastic 会在内部处理这些问题,您只需担心您想索引的数据以及您想在多长时间内访问这些数据。

从使用角度来看,Serverless 将计算与存储分离,为搜索索引提供了自动扩展功能。这样,您就可以只增长实际需要的资源。

客户端会进行以下调整,以连接到无服务器:

  • 关闭嗅探,忽略任何与嗅探相关的选项

  • 忽略配置中传递的除第一个节点外的所有节点,并忽略任何节点过滤和选择选项

  • 启用压缩和 "TLSv1_2_method"(与为弹性云配置时相同)

  • 为所有请求添加 "elastic-api-version "HTTP 头信息

  • 默认使用 "云连接池",而不是 "加权连接池

  • 关闭卖方 "内容类型 "和 "接受 "标头,转而使用标准 MIME 类型

要连接无服务器项目,需要使用参数 serverMode:serverless。

const { Client } = require('@elastic/elasticsearch')
const client = new Client({
  node: 'ELASTICSEARCH_ENDPOINT',
  auth: { apiKey: 'ELASTICSEARCH_API_KEY' },
  serverMode: "serverless",
});

如何在函数即服务环境中运行客户端

在示例中,我们使用了 Node.js 服务器,但您也可以使用功能即服务环境连接 AWS lambda、GCP Run 等功能。

'use strict'

const { Client } = require('@elastic/elasticsearch')

const client = new Client({
  // client initialisation
})

exports.handler = async function (event, context) {
  // use the client
}

另一个例子是连接像 Vercel 这样的服务,它也是无服务器的。您可以查看这个完整的示例,了解如何做到这一点,但搜索端点最相关的部分如下所示:

const response = await client.search(
  {
    index: INDEX,
    // You could directly send from the browser
    // the Elasticsearch's query DSL, but it will
    // expose you to the risk that a malicious user
    // could overload your cluster by crafting
    // expensive queries.
    query: {
      match: { field: req.body.text },
    },
  },
  {
    headers: {
      Authorization: `ApiKey ${token}`,
    },
  }
);

该端点位于 /api 文件夹中,从服务器端运行,因此客户端只能控制与搜索词相对应的 "文本 "参数。

使用 "功能即服务 "的意义在于,与全天候运行的服务器不同,功能只启动运行该功能的机器,一旦完成,机器就会进入休息模式,以减少资源消耗。

如果应用程序没有收到太多请求,这种配置会很方便;否则,成本会很高。您还需要考虑函数的生命周期和运行时间(在某些情况下可能只有几秒钟)。

结论

在本文中,我们学习了如何处理错误,这在生产环境中至关重要。我们还介绍了在模拟 Elasticsearch 服务的过程中测试应用程序的方法,无论集群的状态如何,这种方法都能提供可靠的测试,让我们专注于我们的代码。

最后,我们演示了如何通过配置 Elastic Cloud Serverless 和 Vercel 应用程序来启动完全无服务器堆栈。

相关内容

使用 JavaScript、Mastra 和 Elasticsearch 构建代理 RAG 助手

JD Armada

正确使用 JavaScript 的 Elasticsearch,第一部分

Jeffrey Rengifo

准备好打造最先进的搜索体验了吗?

足够先进的搜索不是一个人的努力就能实现的。Elasticsearch 由数据科学家、ML 操作员、工程师以及更多和您一样对搜索充满热情的人提供支持。让我们联系起来,共同打造神奇的搜索体验,让您获得想要的结果。

亲自试用