亚马逊AWS官方博客

Amazon Elasticsearch Service 中的韩语分析程序支持

Amazon Elasticsearch Service (AES) 中需要改进的韩语分析程序

在处理中文、日语和韩语等亚洲语言的搜索内容时,我们面临着独特的挑战。通过搜索引擎完成的常规处理操作 (例如,基于空格的语汇单元化以及通过提取词根来推导原形) 对于亚洲语言来说是不够的,因为许多词语都是复合词,而且根据复合词所在的上下文,词语的含义有所不同。AES 增加了对广泛使用的开源韩语分析程序 (即 Seunjeon 插件) 的支持,从而增强了对韩语处理的支持。宣布支持 Seunjeon 插件的发布会在一篇博文中进行了说明,其中介绍了将该插件与 AES 结合使用的步骤。作为支持 Seunjeon 插件的一部分,我们对该插件进行了多项优化,以便减少其内存占用量,这样,只需较少的内存即可运行实例。本博文将介绍在做出支持 Seunjeon 插件的决定背后所进行的分析,并且还将详细介绍为降低堆利用率而进行的内存优化。

为 Amazon Elasticsearch Service 选择最佳韩语分析插件

在决定支持 Seunjeon 插件之前,我们对多个韩语分析插件进行了评估。以下列出了我们考虑的主要备选插件以及每种插件的优缺点:

  1. elasticsearch-analysis-openkoreantext 此插件支持所有 Elasticsearch 版本,最高支持 6.1.1。它使用 Twitter opensource korean-open-text 模块,该模块具有功能非常强大的韩语分析程序和内置字典,并且已获得 Apache License Version 2.0 授权许可。此插件的缺点是社区活跃度较低,并且无法为语汇单元化添加自定义内联字典。尽管该插件支持以文件形式添加自定义字典,但需要将这些字典添加到无法动态调整的预先确定位置处的插件文件夹内部,因此,添加自定义字典文件很困难。
  2. elasticsearch-twitter-korean 此插件仅支持 2.3 及更早版本的 Elasticsearch。
  3. open-korean-text-elastic-search 此插件已弃用,不再进行维护。
  4. Seunjeon 此插件支持所有 Elasticsearch 版本,最高支持 6.1.1,并且在内部利用 mecab-ko-dic 执行文本分析。它已获得 Apache License Version 2.0 授权许可,并且可以指定自定义内联字典以及相对文件路径。此插件有一个小缺点,那就是需要编辑插件描述符文件才能将其用于较高版本的 Elasticsearch (> 5.4)。

我们选择了 Seunjeon 插件,它具备我们所需的主要功能 (例如支持自定义字典),并且在开源社区中得到了积极开发。

对 Seunjeon 插件进行的优化

在此部分中,我们将详细介绍在堆优化之前执行的堆分析。

堆分析

AES 支持在较小的实例类型 (例如 RAM (2 GB) 有限的 t2.small) 上运行 Elasticsearch。在测试 Seunjeon 插件期间,我们发现,它使用的堆过多 (大约 560 MB)。此等级别的堆利用率会导致较小的实例类型经常耗尽内存。因此,我们决定优化该插件的内存占用量。我们使用 Eclipse MAT 插件来分析堆,并使用 MAT 工具提供的“泄漏疑点报告”来识别该插件使用的最大对象,如以下屏幕截图所示:

MAT 工具提供的“泄漏疑点报告”

从泄漏疑点报告中可以看出,使用堆最多的对象是属于该插件使用的“Morpheme”类的对象数组。我们利用泄漏疑点报告中的详细信息继续检查哪些类使用“Morpheme”对象数组,该报告显示了对象的所有出站引用,如以下屏幕截图中所示:

显示对象所有出站引用的泄漏疑点报告中的详细信息

详细的报告分析表明,具有 Morpheme 类数组最多的成员是“leixconDict”对象,进而引导我们检查“lexiconDict”中的内容,具体如下所示:

“lexiconDict”中的内容

如上所示,“lexiconDict”对象的“termDict”成员包含 Morpheme 数组,并且是堆占用最多的对象 (大约为 477 MB)。以下是我们使用 Scala 中的插件源代码显示的 Morpheme 类结构:

@SerialVersionUID(1000L)
case class Morpheme(var surface:String,
                    var leftId:Short,
                    var rightId:Short,
                    var cost:Int,
                    var feature:mutable.WrappedArray[String],
                    var mType:MorphemeType,
                    var poses:mutable.WrappedArray[Pos]) extends Serializable {

  //class logic goes here
}

Morpheme 类有一个名为“feature”的成员变量,该变量是一个字符串数组并且是最大的对象。对于每个 Morpheme 对象 (大小约为 664 字节),单单是“feature”数组就占用 520 字节的内存。我们深入分析 feature 数组的内容,如下所示:

feature 数组的内容

在此粒度分析级别,我们充分了解了堆利用,决定进行优化。

执行的堆优化

Morpheme 的 feature 数组包含用于存储单词不同特性 (例如,单词是名词还是代词) 的元数据。在上面的屏幕截图示例中,您可以看到两个 feature 数组含有相同的单词“NNP”,它在数组之间重复出现多次,甚至在同一数组中也多次出现。同一“NNP”字符串的各个副本指向不同的内存位置,这意味着系统会为数组中的每个单词创建一个新的字符串对象。由于字符串不可变,因此,同一底层字符串无需具有多个对象 – 字符串对象可以指向相同的内存位置。这引发了我们的首次优化,在此优化中,我们使用字符串标准化来识别重复字符串,并将其指向单一版本的字符串 (单一内存位置)。我们为字符串构建缓存 (如下所示),并将其转换为 UTF-8 字符编码,以便减少所用的空间。

private static Map<String, byte[]> stringCache = new ConcurrentHashMap<String, byte[]>(1000);
public static byte[] compressStr(String str) {
        if (stringCache.containsKey(str)) {
            return stringCache.get(str);
        }
        final byte[] compressedStringBytes = compress(str.getBytes(UTF_8));
        if (stringCache.size() >= 10000) {
            stringCache.clear();
        }
        stringCache.putIfAbsent(str, compressedStringBytes);
        return compressedStringBytes;

    }

应用字符串标准化之后,feature 数组元素如下所示:

应用字符串标准化后的 feature 数组元素

在以上屏幕截图中,您可以看到,字符串数组“NNP”中的重复条目现在指向相同的内存位置。以“NNP”作为值的数组中的索引位置 0、5 和 6 现在指向相同的内存位置 0x75ac86550。此优化将“NNP”条目缩减到 48 字节,而它原来在三个不同的索引位置存储整个值需要 144 字节。此外,我们还优化了 Morpheme 对象的其他成员变量。例如“mType”,这是在 Scala 中声明如下的 MorphemeType 类型的 ENUM:

var mType:MorphemeType

我们将 ENUM 转换为如下所示的一个字节,因为它只有 4 个底层值,可以使用一个字节表示。优化之前,类型为 MorphemeType 的每个 ENUM 占用 32 字节,而优化后仅占用一个字节。

var _mType: Byte

结论

执行上述优化之后,Seunjeon 插件使用的堆从 560 MB 减少到 271.5 MB,总体减少了 51%。Seunjeon 插件的用户可以利用随 Amazon Elasticsearch Service 提供的插件中的堆优化,以及独立的开源版本。Amazon Elasticsearch Service 提供堆经过优化且开箱即用的 Seunjeon 插件。对于开源版本,我们的堆优化的 Pull 请求位于 https://bitbucket.org/eunjeon/seunjeon/pull-requests/11/optimizing-heap-utilization/diff。目前,在开源版本中,堆经过优化的版本默认仅适用于堆大小不超过 1GB 的设备;如果堆大小超过 1GB,堆优化会以选件形式提供。您现在可以使用 Seunjeon 插件确保对韩语文本进行高保真度解析和匹配,而且所占用的内存减少。


Pallavi Priyadarshini Pallavi Priyadarshini Pallavi 是 Amazon Web Services 的工程部经理,负责领导高性能搜索技术的设计和开发。在加入 AWS 之前,她曾领导全球团队致力于多款分析和数据库产品的研发,并与企业客户就关键任务型应用程序展开密切合作。

Vengadnathan Srinivasan

Vengadnathan Srinivasan Vengadanathan 是 Amazon Web Services 的软件开发工程师。他热衷解决分布式系统相关问题。另外,他还是一名 Java 爱好者,喜欢优化代码以便提高性能。闲暇时间,他喜欢听音乐,阅读科技博文。