云服务器内容精选
-
脚本“perf.lua” local random = math.random local reqs = {} local cnt = 0 -- 压测的查询请求文件名称根据需要调整。 for line in io.lines("requests.txt") do table.insert(reqs, line) cnt = cnt + 1 end local addrs = {} local counter = 0 function setup(thread) local append = function(host, port) for i, addr in ipairs(wrk.lookup(host, port)) do if wrk.connect(addr) then addrs[#addrs+1] = addr end end end if #addrs == 0 then -- 根据集群的实际地址进行修改。 append("x.x.x.x", 9200) append("x.x.x.x", 9200) append("x.x.x.x", 9200) end local index = counter % #addrs + 1 counter = counter + 1 thread.addr = addrs[index] end -- 索引名称根据需要调整。 wrk.path = "/index_sift_graph/_search?request_cache=false&preference=_local" wrk.method = "GET" wrk.headers["Content-Type"] = "application/json" function request() return wrk.format(wrk.method, wrk.path, wrk.headers, reqs[random(cnt)]) end
-
测试前准备 创建Elasticsearch向量数据库,参考创建Elasticsearch集群。 “节点数量”选择“3”,“节点规格”选择“通用计算型”的“4vCPUs | 16GB”(由于测试的数据量不大,且为了和第三方的基线测试保持相同的CPU规格),“节点存储”选择“超高I/O”,不启用安全模式。 获取测试数据集。 sift-128-euclidean:维度128,base数据100万条,使用欧式距离度量。 cohere-768-cosine:维度768,base数据100万条,使用余弦距离度量。 gist-960-euclidean:维度960,base数据100万条,使用欧式距离度量。 “sift-128-euclidean”和“gist-960-euclidean”数据的下载地址是https://github.com/erikbern/ann-benchmarks。如需使用“cohere-768-cosine”数据,请提交工单获取。 图1 下载“sift-128-euclidean”和“gist-960-euclidean”数据 准备测试工具。 准备数据写入和召回率测试脚本,参考脚本base_test_example.py。 下载性能测试使用的开源压测工具Wrk,获取地址https://github.com/wg/wrk/tree/master。
-
性能测试的操作步骤 创建一个弹性 云服务器ECS ,用于安装压测工具和执行测试脚本。操作指导请参见快速购买和使用Linux E CS 。 ECS必须和Elasticsearch集群在同一个虚拟私有云和安全组中。 也可以使用其他客户端服务器,但是必须保证服务器和Elasticsearch集群在同一VPC。 将测试数据集上传到ECS上。 将数据写入和召回率测试脚本上传到ECS上,并执行如下命令。 pip install h5py pip install elasticsearch==7.10 python3 base_test_example.py 执行完成后,会创建测试的向量索引,写入测试数据,并返回平均查询召回率Recall。 在ECS上安装开源压测工具Wrk。 在ECS上准备压测的查询请求文件,用于模拟真实业务场景。参考脚本prepare_query.py。 pip install h5py python3 prepare_query.py 在ECS上准备Wrk的压测配置脚本。参考脚本perf.lua,脚本中的查询请求文件名称、集群访问地址和索引名称需要根据实际环境修改。 在ECS执行如下命令进行向量检索的性能压测。 wrk -c60 -t60 -d10m -s perf.lua http://x.x.x.x:9200 “t”表示压测线程数。 “c”表示与服务端的连接数。 “d”表示压测时间,“10m”表示10分钟 。 “s”表示Wrk的压测配置脚本。 “x.x.x.x”表示Elasticsearch集群的访问地址。 在回显中获得测试数据,其中“Requests/sec”即查询吞吐量QPS。 图2 测试结果示例
-
性能测试比较 GRAPH类索引 百万规模的场景推荐使用GRAPH索引类型。 测试方案一:使用不同维度的数据集,在Top10召回率均达到99%的情况下,测试向量数据库能支撑的最大QPS。每个数据集均基于默认参数和调优参数分别进行测试,通过调整构建参数可以使得图索引结构更优,在同等召回精度下能取得更高的查询性能。 测试结果: 表1 GRAPH类索引测试结果1 数据集 构建参数 查询参数 性能指标 efc shrink ef max_scan_num QPS Recall sift-128-euclidean 200 1.0 84 10000 15562 0.99 500 0.8 50 10000 17332 0.99 cohere-768-cosine 200 1.0 154 10000 3232 0.99 500 0.95 106 10000 3821 0.99 gist-960-euclidean 200 1.0 800 19000 860 0.99 500 0.9 400 15000 1236 0.99 结论:对于不同的数据集,使用默认参数均能达到99%以上的召回率。在进一步调整构建参数和查询参数后,增加了一定的索引构建开销,同时也达到更高的查询性能。 测试方案二:使用同一数据集,通过调整索引参数,测试不同召回率下的查询性能。本方案用COHERE数据集,分别测试了Top10召回率为99%、98%及95%时的集群最大QPS。 测试结果: 表2 GRAPH类索引测试结果1 数据集 构建参数 查询参数 性能指标 efc ef QPS Recall cohere-768-cosine 500 128 3687 0.99 500 80 5320 0.98 500 36 9028 0.95 结论:同一集群在统一索引构建参数的情况下,通过调整ef参数可以获得不同的查询精度,在略微损失召回率的场景下可以获得成倍的性能提升。 GRAPH_PQ类索引 基于图算法的索引为了保证查询性能通常需要常驻内存,因此当向量维度较高或数据量较大时,内存资源成为影响成本及性能的关键因素。具体来说,高维度的向量和大规模的数据集对内存的需求显著增加,这不仅关系到存储成本,还直接影响到索引算法的运行效率和响应速度。该场景推荐使用GRAPH_PQ索引类型。 测试方案:使用维度较高的COHERE与GIST数据集,测试在Top10召回率达到95%时的集群最大QPS,并与GRAPH索引对比常驻内存开销。 测试结果: 表3 GRAPH_PQ类索引测试结果 数据集 构建参数 查询参数 性能指标 内存开销 efc fragment_num ef topk QPS Recall GRAPH_PQ GRAPH cohere-768-cosine 200 64 85 130 8723 0.95 332MB 3.3GB gist-960-euclidean 200 120 200 360 4267 0.95 387MB 4.0GB 结论:结果显示使用GRAPH_PQ类索引能够在节约10倍+内存开销的情况下,取得与GRAPH索引差不多的精度和性能。因此, CSS 向量索引的GRAPH_PQ算法融合了图索引与量化算法,能够大幅降低内存的开销,提升单机的数据容量。 测试数据中涉及的索引参数说明请参见表4,关于构建参数的详细说明请参见在Elasticsearch集群创建向量索引,关于查询参数的详细说明请参见在Elasticsearch集群使用向量索引搜索数据。 表4 索引参数说明 类型 参数名称 说明 构建参数 efc 构建hnsw时考察邻居节点的队列大小,默认值为200,值越大精度越高,构建速度将会变慢。 shrink 构建hnsw时的裁边系数,默认值为1.0f。 fragment_num 段数,默认值为0,插件自动根据向量长度设置合适的段数。 查询参数 ef 查询时考察邻居节点的队列大小。值越大查询精度越高,查询速度会变慢。默认值为200。 max_scan_num 扫描节点上限。值越大精度越高,查询速度变慢。默认值为10000。 topk 查询时返回top k条数据。
-
脚本“base_test_example.py” # -*- coding: UTF-8 -*- import json import time import h5py from elasticsearch import Elasticsearch from elasticsearch import helpers def get_client(hosts: list, user: str = None, password: str = None): if user and password: return Elasticsearch(hosts, http_auth=(user, password), verify_certs=False, ssl_show_warn=False) else: return Elasticsearch(hosts) # 索引参数说明请参见在Elasticsearch集群创建向量索引。 def create(es_client, index_name, shards, replicas, dim, algorithm="GRAPH", metric="euclidean", neighbors=64, efc=200, shrink=1.0): index_mapping = { "settings": { "index": { "vector": True }, "number_of_shards": shards, "number_of_replicas": replicas, }, "mappings": { "properties": { "id": { "type": "integer" }, "vec": { "type": "vector", "indexing": True, "dimension": dim, "algorithm": algorithm, "metric": metric, "neighbors": neighbors, "efc": efc, "shrink": shrink, } } } } es_client.indices.create(index=index_name, body=index_mapping) print(f"Create index success! Index name: {index_name}") def write(es_client, index_name, vectors, bulk_size=1000): print("Start write! Index name: " + index_name) start = time.time() for i in range(0, len(vectors), bulk_size): actions = [{ "_index": index_name, "id": i + j, "vec": v.tolist() } for j, v in enumerate(vectors[i: i + bulk_size])] helpers.bulk(es_client, actions, request_timeout=180) print(f"Write success! Docs count: {len(vectors)}, total cost: {time.time() - start:.2f} seconds") merge(es_client, index_name) def merge(es_client, index_name, seg_cnt=1): print(f"Start merge! Index name: {index_name}") start = time.time() es_client.indices.forcemerge(index=index_name, max_num_segments=seg_cnt, request_timeout=7200) print(f"Merge success! Total cost: {time.time() - start:.2f} seconds") # 查询参数说明请参考见在Elasticsearch集群使用向量索引搜索数据。 def query(es_client, index_name, queries, gts, size=10, k=10, ef=200, msn=10000): print("Start query! Index name: " + index_name) i = 0 precision = [] for vec in queries: hits = set() dsl = { "size": size, "stored_fields": ["_none_"], "docvalue_fields": ["id"], "query": { "vector": { "vec": { "vector": vec.tolist(), "topk": k, "ef": ef, "max_scan_num": msn } } } } res = es_client.search(index=index_name, body=json.dumps(dsl)) for hit in res['hits']['hits']: hits.add(int(hit['fields']['id'][0])) precision.append(len(hits.intersection(set(gts[i, :size]))) / size) i += 1 print(f"Query complete! Average precision: {sum(precision) / len(precision)}") def load_test_data(src): hdf5_file = h5py.File(src, "r") base_vectors = hdf5_file["train"] query_vectors = hdf5_file["test"] ground_truths = hdf5_file["neighbors"] return base_vectors, query_vectors, ground_truths def test_sift(es_client): index_name = "index_sift_graph" vectors, queries, gts = load_test_data(r"sift-128-euclidean.hdf5") # 根据实际测试需求调整分片和副本数、索引算法、索引参数等。本文性能测试均配置的是1个分片、2个副本。 create(es_client, index_name, shards=1, replicas=2, dim=128) write(es_client, index_name, vectors) query(es_client, index_name, queries, gts) if __name__ == "__main__": # 此处修改为CSS集群的实际访问地址。 client = get_client(['http://x.x.x.x:9200']) test_sift(client)
-
写入性能优化 基于Elasticsearch的数据写入流程分析,有以下几种性能优化方案。 表1 写入性能优化 优化方案 方案说明 使用SSD盘或升级集群配置 使用SSD盘可以大幅提升数据写入与merge操作的速度,对应到CSS服务,建议选择“超高IO型”存储,或者超高IO型主机。 采用Bulk API 客户端采用批量数据的写入方式,每次批量写入的数据建议在1~10MB之间。 随机生成_id 如果采用指定_id的写入方式,数据写入时会先触发一次查询操作,进而影响数据写入性能。对于不需要通过_id检索数据的场景,建议使用随机生成的_id。 设置合适的分片数 分片数建议设置为集群数据节点的倍数,且分片的大小控制在50GB以内。 关闭副本 数据写入与查询错峰执行,在数据写入时关闭数据副本,待数据写入完成后再开启副本。 Elasticsearch 7.x版本中关闭副本的命令如下: PUT {index}/_settings { "number_of_replicas": 0 } 调整索引的刷新频率 数据批量写入时,可以将索引的刷新频率“refresh_interval”设置为更大的值或者设置为“-1”(表示不刷新),通过减少分片刷新次数提高写入性能。 Elasticsearch 7.x版本中,将更新时间设置为15s的命令如下: PUT {index}/_settings { "refresh_interval": "15s" } 优化写入线程数与写入队列大小 为应对突发流量,可以适当地提升写入线程数与写入队列的大小,防止突发流量导致出现错误状态码为429的情况。 Elasticsearch 7.x版本中,可以修改如下自定义参数实现写入优化:thread_pool.write.size,thread_pool.write.queue_size。 设置合适的字段类型 指定集群中各字段的类型,防止Elasticsearch默认将字段猜测为keyword和text的组合类型,增加不必要的数据量。其中keyword用于关键词搜索,text用于全文搜索。 对于不需要索引的字段,建议“index”设置为“false”。 Elasticsearch 7.x版本中,将字段“field1”设置为不建构索引的命令如下: PUT {index} { "mappings": { "properties": { "field1":{ "type": "text", "index": false } } } } 优化shard均衡策略 Elasticsearch默认采用基于磁盘容量大小的Load balance策略,在多节点场景下,尤其是在新扩容的节点上,可能出现shard在各节点上分配不均的问题。为避免这类问题,可以通过设置索引级别的参数“routing.allocation.total_shards_per_node”控制索引分片在各节点的分布情况。此参数可以在索引模板中配置,也可以修改已有索引的setting生效。 修改已有索引的setting的命令如下: PUT {index}/_settings { "index": { "routing.allocation.total_shards_per_node": 2 } }
-
数据写入流程 图1 数据写入流程 如图1所示,以Elasticsearch集群为例,介绍客户端往Elasticsearch或OpenSearch集群中写入数据的流程。图中的P表示主分片Primary,R表示副本分片Replica,主副分片在数据节点Node里是随机分配的,但是不能在同一个节点里。 客户端向Node1发送写数据请求,此时Node1为协调节点。 节点Node1根据数据的_id将数据路由到分片2,此时请求会被转发到Node3,并执行写操作。 当主分片写入成功后,它将请求转发到Node2的副本分片上。当副本写入成功后,Node3将向协调节点报告写入成功,协调节点向客户端报告写入成功。 Elasticsearch中的单个索引由一个或多个分片(shard)组成,每个分片包含多个段(Segment),每一个Segment都是一个倒排索引。 图2 Elasticsearch的索引组成 如图3所示,将文档插入Elasticsearch时,文档首先会被写入缓冲区Buffer中,同时写入日志Translog中,然后在刷新时定期从该缓冲区刷新文档到Segment中。刷新频率由refresh_interval参数控制,默认每1秒刷新一次。更多写入性能相关的介绍请参见Elasticsearch的官方介绍Near Real-Time Search。 图3 文档插入Elasticsearch的流程
-
解决方案 方案一:关闭或者删除不用的索引,减少shard数量。 方案二:修改节点的shard数量的限制,参数配置请参考max_shards_per_node。 PUT _cluster/settings{ "persistent": { "cluster": { "max_shards_per_node": 2000 } }} 修改节点的shard数量的限制属于临时规避方案,如果要长期解决,建议每GB的JVM堆内存小于等于20个shards(节点堆内存大小为节点内存规格的1/2,最大值为31GB),shard的建议数量详细请参见shard count recommendation。
-
集群一直处于快照中 集群一直处于快照中,有三个比较常见的原因: 集群数据量大或者集群压力大,备份快照耗时长。 单个节点的快照速度默认是40MB/s,同时,快照的性能还受集群情况影响,如果此时集群负载较高,耗时将会更久。可以通过上述章节的查询单个快照信息查询正在执行的快照情况。 执行GET _snapshot/repo_auto/snapshot-name,可以看到剩余还需要完成的shard个数,也可以通过删除快照接口提前终止。 解决方法:等待或者提前终止。 快照信息更新失败。 Elasticsearch将进行中的快照信息保存在cluster state中,快照完成后需要更新快照状态,由于Elasticsearch更新快照状态的接口没有加入重试或者容错机制,比如由于当时集群内存压力大,更新快照动作被熔断,那么这个快照将会一直处于快照中。 解决方法:调用快照删除接口。 临时AK、SK过期。 CSS通过委托将Elasticsearch中的数据写入到用户的OBS中,快照仓库创建的时候,需要去使用委托获取临时的AK 、SK设置到仓库中。由于临时的AK、SK是有时效性的(24小时过期),如果一个快照超过24小时还未完成,那么这个快照将会失败。这种情况会有一个比较大的风险,因为此时仓库的AK、SK过期,无法对这个仓库进行更新、查询、删除操作,这种情况下cluster state信息将会无法清除,只能通过普通重启(滚动重启无法生效)集群来清除cluster state里面残留的快照信息。 解决方法:暂时只能通过普通重启集群来消除,后期CSS会提供终止接口,可以来解决这种无法消除状态的现象。 父主题: 功能使用类
-
排查是否有权限 登录 统一身份认证 服务管理控制台。 查看当前登录所用的账号或 IAM 用户所属的用户组。 具体操作请参见《统一身份认证服务用户指南》中的查看或修改用户信息章节。 查看用户组的权限中是否包含:“全局服务”中“ 对象存储服务 ”项目的“Tenant Administrator”权限、当前所属区域的“Elasticsearch Administrator”权限。 具体操作请参见《统一身份认证服务用户指南》中的查看或修改用户组章节。 如果用户组的权限中不包含以上两个权限,请执行4。 如果用户组的权限中包含以上两个权限,请联系人工客服协助解决。 为用户组添加:“全局服务”中“对象存储服务”项目的“Tenant Administrator”权限、当前所属区域的“Elasticsearch Administrator”权限。 具体操作请参见《统一身份认证服务用户指南》中的查看或修改用户组章节。
-
问题现象 ECS服务器部署logstash,然后推送数据到 云搜索服务 CSS,出现错误信息如下: LogStash::Outputs::ElasticSearch::HttpClient::Pool::BadResponseCodeError: Got response code '500' contacting Elasticsearch at URL 'https://192.168.xx.xx:9200/_xpack'。
-
为什么集群创建失败 集群创建失败原因有如下4种: 资源配额不足,无法创建集群。建议申请足够的资源配额,详情请参见如何申请扩大配额?。 如果集群配置信息中,“安全组”的“端口范围/ICMP类型”不包含“9200”端口,导致集群创建失败。请修改安全组信息或选择其他可用安全组。 7.6.2以及7.6.2之后的版本,集群内通信端口9300默认开放在用户VPC的子网上面。创建集群时需要确认所选安全组是否放通子网内的9300通信端口,如果未放通,请修改安全组信息或选择其他可用安全组。 权限不足导致集群创建失败。建议参考权限管理获取权限,然后创建集群。 父主题: 访问集群类
-
处理步骤 在集群管理页面,单击不可用的集群名称,进入集群基本信息页面。 单击“配置信息”中的安全组名称,进入当前集群所选安全组的基本信息页面。 分别查看“入方向规则”和“出方向规则”页签下,是否存在“策略”为“允许”,“协议端口”为“TCP : 9300”,“类型”为“IPv4”的安全组规则。 是,联系技术支持定位集群不可用问题。 否,执行下一步。 修改集群当前所选安全组信息,放通9300通信端口。 在当前集群所选安全组基本信息界面,选择“入方向规则”页签。 单击“添加规则”,在添加入方向规则对话框设置“优先级”为“100”,“策略”选择“允许”,“协议端口”选择“基本协议/自定义TCP”,端口填写“9300”,“类型”选择“IPv4”,“源地址”选择“安全组”下的集群当前安全组名称,即同安全组内放通。 图2 添加安全组规则 单击“确定”即可完成放通9300端口的设置。 同样的步骤,在“出方向规则”页签添加放通9300端口的设置。 安全组放通9300端口后,等待集群自动恢复可用状态。
-
问题现象 安装自定义插件后重启集群,“集群状态”变为“不可用”。 单击集群名称进入集群基本信息页面,选择“日志管理”,单击“日志查询”页签,可见日志内容存在明显的关于插件的报错“fatal error in thread [main], exitingjava.lang. NoClassDefFoundError: xxx/xxx/.../xxxPlugin at ...”。 图1 节点报错日志示例 CSS服务已下线自定义插件功能,但历史版本的集群可能还装有自定义插件,只有这类集群可能出现该故障。
-
问题现象 “集群状态”为“不可用”。 单击集群名称进入集群基本信息页面,选择“日志管理”,单击“日志查询”页签,可见日志内容存在警告“master not discovered or elected yet, an election requires at least 2 nodes with ids [xxx, xxx, xxx, ...], have discovered [xxx...] which is not a quorum”。 图1 节点报错日志示例
更多精彩内容
CDN加速
GaussDB
文字转换成语音
免费的服务器
如何创建网站
域名网站购买
私有云桌面
云主机哪个好
域名怎么备案
手机云电脑
SSL证书申请
云点播服务器
免费OCR是什么
电脑云桌面
域名备案怎么弄
语音转文字
文字图片识别
云桌面是什么
网址安全检测
网站建设搭建
国外CDN加速
SSL免费证书申请
短信批量发送
图片OCR识别
云数据库MySQL
个人域名购买
录音转文字
扫描图片识别文字
OCR图片识别
行驶证识别
虚拟电话号码
电话呼叫中心软件
怎么制作一个网站
Email注册网站
华为VNC
图像文字识别
企业网站制作
个人网站搭建
华为云计算
免费租用云托管
云桌面云服务器
ocr文字识别免费版
HTTPS证书申请
图片文字识别转换
国外域名注册商
使用免费虚拟主机
云电脑主机多少钱
鲲鹏云手机
短信验证码平台
OCR图片文字识别
SSL证书是什么
申请企业邮箱步骤
免费的企业用邮箱
云免流搭建教程
域名价格