Laravel Scout集成AWS OpenSearch搜索时触发illegal_argument_exception
问题背景
在Laravel 10项目中使用zingimmick/laravel-scout-opensearch驱动,由于数据表无自增ID,无法通过php artisan scout:import命令同步数据,因此手动执行以下代码同步了50条Asset模型数据:
Asset::query() ->limit(50) ->get() ->searchable();
Asset模型代码如下:
class Asset extends Model { use Searchable; public $timestamps = false; protected $fillable = [ 'policy_id', 'asset_id', 'fingerprint', 'asset_name', ]; public function searchableAs(): string { return 'assets_index'; } public function getScoutKey(): string { return $this->policy_id . $this->asset_id; } }
数据已成功同步到OpenSearch的assets_index索引中,但执行搜索代码时触发错误:
Text fields are not optimised for operations that require per-document field data like aggregations and sorting, so these operations are disabled by default. Please use a keyword field instead. Alternatively, set fielddata=true on [id] in order to load field data by uninverting the inverted index. Note that this can use significant memory.
问题根源
Laravel Scout默认会将getScoutKey()返回的值同时作为OpenSearch文档的_id和文档中的id字段存储,但该id字段在索引中被自动映射为text类型。而Scout在获取搜索结果时,需要对id字段进行排序或聚合操作,text类型默认不支持这类操作,因此抛出上述错误。
解决步骤
方案一:自定义Scout主键字段名(推荐)
在Asset模型中新增getScoutKeyName()方法,指定自定义的主键字段名,避免使用默认的id字段:
class Asset extends Model { use Searchable; public $timestamps = false; protected $fillable = [ 'policy_id', 'asset_id', 'fingerprint', 'asset_name', ]; public function searchableAs(): string { return 'assets_index'; } public function getScoutKey(): string { return $this->policy_id . $this->asset_id; } // 指定自定义主键字段名 public function getScoutKeyName(): string { return 'asset_key'; } }
之后需要:
- 通过OpenSearch控制台或API删除已有的
assets_index索引 - 重新执行数据同步代码:
Asset::query() ->limit(50) ->get() ->searchable();
此时,OpenSearch文档的_id仍会使用getScoutKey()的值,而文档内的主键字段会是asset_key(自动映射为keyword类型),Scout处理结果时不会再触发text字段的操作限制。
方案二:修改现有索引的id字段映射(不推荐)
如果不想重新创建索引,可以通过OpenSearch API修改id字段的映射,将其改为keyword类型:
PUT /assets_index/_mapping { "properties": { "id": { "type": "keyword" } } }
或者开启fielddata=true(会占用大量内存,不推荐):
PUT /assets_index/_mapping { "properties": { "id": { "type": "text", "fielddata": true } } }
内容的提问来源于stack exchange,提问作者Latheesan

