扫一扫
分享文章到微信
扫一扫
关注官方公众号
至顶头条
我们知道一个很棒的应用程序最大的缺憾之一是,速度太慢以至于无法使用 —— 由于其非常慢的响应速度而使得用户的大量时间和辛苦工作都被浪费了。在过去的 12 年里,我们花费了大量的时间研究和测试 Domino 应用程序及其功能,以了解如何优化性能以及哪些特性最适合用来优化性能。我们在 20 世纪 90 年代早期开始支持和开发 Domino 应用程序,并且很快就被应用程序的性能所吸引。许多我们当时(今天仍然是这样)认为是服务器的性能问题实际上都是应用程序的性能问题。因此解决方案经常是在应用程序内部找到的,而不是在服务器上找到的。
在这个由两部分组成的文章系列中,我们将与您分享一些我们知道的东西。这个文章系列覆盖了应用程序性能的三个方面:数据库属性、文档集合以及视图。在第 1 部分中,我们将探讨数据库属性和文档集合。在所有情况下,我们都会指出最重要的地方并提供简洁的现实示例,以帮助您理解如何优化自己的应用程序。我们将使用来自许多应用程序的示例;在这些示例中至少能够找到一个与您现在所要做的或者是所使用的类似。我们的目标是帮助您构建既好又快的应用程序。
本文假设您是一位经验丰富的 Notes/Domino 应用程序开发人员。
数据库属性
有一些与应用程序性能相关的数据库属性。
不维护未读标记
如果选中该复选框,那么不管对每个视图的设置如何,在应用程序中都不会跟踪未读标记。我们使用 client_clock 来跟踪打开一个数据库所花费的时间,我们的发现令人吃惊。对于一个大型应用程序(比如说带有 200,000 个文档的 20 GB 的应用程序),在不使用未读标记时,Notes 客户机只需大约五秒钟的时间就能够打开数据库(包括网络传输)。如果打开了未读标记,必须多等六秒或更多的时间。这些多出来的时间是花费在 GET_UNREAD_NOTE_TABLE 和 RCV_UNREAD 上的。如果关闭了未读标记,就不会进行这些调用。
在一个小一些的数据库(小于 1 GB)中,关闭未读标记可以节省大约 0.5 秒钟的时间。当然,与大型数据库相比,不管是否打开未读标记,打开小数据库的时间都要更快一些。所以在将应用程序投入运行前,应该考虑是否需要使用未读标记特性。
优化文档表映射
该特性在过去的几个 Lotus Notes/Domino 版本中都没有改变。该特性的设计目的是提高具有与 Domino 目录相似的结构的应用程序的视图索引速度。(换言之,这些应用程序包含许多使用同一表单的文档,并有很少的文档使用不同的表单,就像 Domino 目录中的个人文档和服务器文档那样。)
思路是这样的,不要查看每个文档的注释以确定是否将该文档包含到视图索引中,而是要进行两步操作。第一步只检查文档是否关联了正确的表单名称。如果需要,第二步检查将文档注释包含在视图索引中所必须要满足的各种其他条件。
注:目前,这一特性看起来并不能减少索引时间,即使用于 Domino 目录也是这样。
不覆盖空闲空间
该特性在过去的几个 Lotus Notes/Domino 版本中都没有改变。如果未选中该复选框,那么一旦将文档删除,Lotus Notes 就会将数据覆盖,而不是仅仅删除指向该数据的指针。覆盖的目的是使数据不可恢复。只有在担心硬盘的物理安全时才有必要使用该功能。对于真实环境的每一个应用程序,这个额外的物理安全措施是没有必要的,它只是增加了删除数据的额外步骤。
维护 LastAccessed 属性
该特性在过去的几个 Lotus Notes/Domino 版本中都没有改变。如果选中该复选框,Notes 将跟踪 Notes 客户机打开数据库中每个文档的最后时间。Lotus Notes 总是跟踪最后保存时间(当然是保存在 $UpdatedBy 字段中),但是该特性还会跟踪最后读取时间。(然而,它并不跟踪 Web 浏览器的读取操作。)
除了在知识库应用程序中,我们没有发现开发人员使用该特性;在知识库应用程序中,如果数据在规定的几个月或几年的时间内没有被读取,那么会将该数据存档。
文档集合
我们已经研究客户在代理、视图、表单域公式等中编写的代码很多年了。根据我们的经验,由于很多的原因,前端性能问题往往比后端性能问题更加麻烦:
但是不管代码来自何处,如果发现系统很慢并打开代码进行检查时,我们很可能会发现下列共同特征:
从多年的性能测试中,我们发现上面第一种操作的速度十分快,并不需要优化,至少在找到更大的问题并解决之前可以不理会它。上面的第三种操作通常很慢,但不幸的是解决问题的余地很小。也就是说,不太可能发现从一组文档中读取信息或存入信息的代码效率低下。例如,试图保存当前日期到名称为 DateToday 的字段中,可能使用下列方法之一:
扩展类
Set Doc = dc.getfirstdocument
Do while not ( Doc is Nothing )
Doc.DateToday = Today
Call Doc.Save
Set Doc = dc.getnextdocument ( Doc )
Loop
ReplaceItemValue
Set Doc = dc.getfirstdocument
Do while not ( Doc is Nothing )
Call Doc.ReplaceItemValue ( "DateToday", Today )
Call Doc.Save
Set Doc = dc.getnextdocument ( Doc )
Loop
StampAll
Call dc.StampAll ( "TodayDate", Today )
在我们的测试中,对于上述三个示例中的前两个,没有发现任何性能上的不同。使用扩展类语法 —— doc.DateToday = Today,看起来与使用 doc.ReplaceItemValue("DateToday", Today)一样快。理论上讲,我们应该发现一些不同之处,因为在其中的一种情况下我们没有显式地向 Lotus Notes 声明将要更新一个字段条目,所以 Lotus Notes 应该花费更长一些的时间来断定 DateToday 实际上是一个字段。然而,实际的测试并没有表现出任何不同。
如果像在前面的例子中那样,使用单一值更新多个文档,那么 dc.StampAll 方法会更快一些。在一些版本中存在一个缺陷,使该方法变得很慢,所以如果您使用的不是最新、最完整的版本,那么请确认该方法能够以最优状态工作(可以通过测试或检查修复列表进行确认)。但是如果使用的是 Lotus Notes/Domino 6.5 和 7 版本,那么该方法的速度是很快的。然而,如果需要经常检查数据或者是将变量数据写入到文档中,那么 dc.StampAll 并不总是一个可行的选项。我们可以将其看作一条有价值的信息,在特定的应用程序中您可以使用该信息,也可以不使用该信息。
在这三个方法中我们应该关注哪一个,我们的经验指出 ReplaceItemValue 示例(获取文档集合)是所要关注的。尽管到目前为止这个方法使用的时间往往是最多的,但是事实证明通常该方法经过优化能够节约最多的时间。这就是我们的测试所关注的内容,在本文的其余部分将进行探讨。
测试
我们的测试方法是使用相同大小的文档(大约 2K)和相同数目的字段(大约 200 个)创建一个大型数据库。确保文档在编写方面具有一些区别,这样我们就能够对任意数目的文档进行查找。尤其是确保能够对 1、2、3 至 9、10 个文档进行查找;还可以对 20、30、40 至 90、100 以及 200、300、400 至 900、1000 个等类似数目的文档进行查找。这样就为我们提供了非常多的数据点,并允许验证不是只在很窄的范围内获得了好的性能。例如 db.search 对于在数据库中搜索较大的文档子集时效率很高,但是在搜索小的子集时效率却很糟糕。如果不在全部的数据点上进行仔细的测试,那么可能会对它的性能特征作出错误的判断。
我们一次运行测试时间长达几个小时,并将结果写到文本文件中,随后会把文本文件导入到电子表格和显示程序中用于绘制 XY 图。在进行了很多这样重复的操作并尝试了小型(1 万个文档)和大型(4 百万个文档)数据库后,得到了一套我们认为对应用程序开发人员有所帮助的指导方针。
哪种方法是最快的?
读取文档集合和向文档集合中进行写入最快的方法是 db.ftsearch 或者 view.GetAllDocumentsByKey。结果是其他方法(参见下面的列表)对于一些数据点上的文档组(本文稍后进行探讨)的性能可能与这两个方法接近,但是对于小型和大型集合,其他方法的性能都无法与这两个方法的性能相比。我们在这里列出了对方法的简要概述,稍后将进行更加详细地论述。
如果有一个小型的文档集合(例如 10 个左右)和一个小型的数据库(例如 10,000 个文档),很多不同的方法都能够获得大致相同的性能,速度都很快。这些可以被称为轻量的情况,在代码不会循环很多次的情况下(或者在应用程序中不会频繁使用),就可以放下这段代码去解决更大的问题。
然而,仍然可以发现微小的差别,如果需要获取很多文档集合,那么每次代码运行即使只节约几分之一秒,意义也非常大。此外,如果应用程序很大(或是不断增大),那么时间上的不同将产生实质性的影响。
这里有两个客户示例:第一个例子,将日程安排代理设置为十分频繁地运行(每几分钟运行或是一旦保存或修改文档就运行),每一个新文档获取搜索条件时都会重复运行,并根据条件执行搜索。如果处理 10 个新文档,那么将执行 10 次搜索;如果处理 100 个新文档,就将执行 100 次搜索。对于该用户,如果将处理一个文档集合的时间缩短 0.5 秒,那么将会节约 10 倍甚至 100 倍的时间,并且还会根据代理的执行频率成倍减少。这样就可以很容易地在一天中传输繁忙的时段每小时节省几分钟的时间,意义是非常重大的。另一个示例是一个主要表单,它具有运行此代码的 PostOpen 或 QuerySave 事件。如果每小时(或更长时间)需要进行成百上千次的编辑操作,0.5 秒的时间节省将会成倍地增加,最后变为一个非常明显的时间节省。
对每个方法的争论
当我们向同事或者客户解释为什么这两种方法比其他方法更快且更容易使用时,经常会进行激烈地争论。让我们感到满意的是,争论越是深入,问题就变得越加清晰。在本文中我们将通过两个争辩对手的对话来说明问题:Prometheus (“Pro” 是他的简称)和怀疑他的同事 Connie(简称 “Con”)。
Prometheus:view.GetAllDocumentsByKey 看起来非常快。我想只要有可能我就愿意使用它。
Connie:好的,我的朋友,但是如果在 Domino 目录中查找数据会怎样呢?您无法轻易地获得创建新视图的许可。
Pro:问得好。行,在应用程序中控制数据库查找,这就是我使用该方法的地方。
Con:哦?如果您最终要在数据库中创建 10 个附加视图,这还是一个好方法吗?想一下需要对所有附加视图进行索引。
Pro:这听起来有些麻烦,但是如果我构建简化的视图,那么在 UPDATE 任务运行时,每 15 分钟索引所用的时间将小于 100 毫秒 —— 如果有查找请求的话,所需要的时间可能会多一些。可以确信,在每个几分钟的时间段内我们可以节省几百毫秒?
Con:您如何简化这些视图?这个过程难吗?需要很多维护操作吗?
Pro:完全不需要维护。要简化一个查找视图,首先使选择条件尽可能精练。这将减小视图索引的大小,进而减少更新索引和执行查找所需的时间。接着,考虑如何对视图进行查找。如果准备获得所有的文档,那么考虑仅使用一个带有公式 “1” 排序了的列。然后就可以在视图中获得所有文档。如果需要多个不同字段的信息,考虑使用第二个列使这些数据点合并到单一查找中。单一查找比多个查找要快得多,即使是返回全部的数据也是这样的。
Con:好,我也乐于使用该方法了。但是您还说过 db.ftsearch 方法也非常快,我并不是十分相信,我正准备使用该方法。看起来它好像需要很多的基础设施。
Pro:是的,要在代码中可靠地使用 db.ftsearch 方法,在维护全文索引的同时,还要确保 Domino 服务器的配置中包含 FT_MAX_SEARCH_RESULTS=n,其中 n 是一个比代码需要返回的最大集合大小还要大的数字。否则,返回的文档数目将会限制在 5,000 个以内。
Con:如果全文索引没有得到足够及时地更新,那么会出现什么样的情况呢?
Pro:如果是那样,可以在代码中包含 db.UpdateFTIndex 来更新索引。
Con:我的测试显示这样做很耗费时间,远比使用 db.ftsearch 方法所节省的时间多。如果根本没有创建全文索引会出现什么情况呢?
Pro:如果数据库中少于 5,000 个文档,将动态创建临时全文索引。
Con:对此我有两点疑问。首先,临时索引效率非常低,因为在代码运行后它才开始填充。其次,5,000 个文档并不是一个较大的临界值。听起来好像我的组织中只能有一些邮件文件。如果数据库中的文档数目超过 5,000 会怎样呢?
Pro:在这种情况下,使用 db.UpdateFTIndex (True)将创建一个永久性的全文索引。
Con:好,但是为较大的数据库创建全文索引会花费很长的时间。我还知道,对于代码而言只有数据库在本地才能够创建全文索引 —— 也就是说代码和数据库要在同一台服务器上执行。
Pro:非常正确。幸运的是,Lotus Notes/Domino 7 具有一些改进了的控制台日志,以及使用 Domino Domain Monitoring(DDM)更进一步地跟踪问题(如使用 ftsearch 方法对未进行全文索引的数据库进行搜索)的能力。在控制台日志中可以看到两条消息。正如您所看到的,这两条消息非常清晰:
Agent Manager: Full text operations on database 'xyz.nsf' which is not full text indexed. This is extremely inefficient.
mm/dd/yyyy 04:04:34 PM Full Text message: index of 10000 documents exceeds limit (5000), aborting: Maximum allowable documents exceeded for a temporary full text index
Con:好了,我看到了。我发现对于 view.ftsearch、view.GetAllEntriesByKey 或 db.search 您并没有说很多正面的东西。我想我知道这其中的原因。前两种方法在一些情况下很快,但是如果恰巧已经对视图进行了结构化,那么所要查找的数据被索引到底部,这样速度就会很慢。并且对于小的文档集合 db.search 效率非常低。
Pro:这些观点都是正确的。然而,db.search 在时间/日期敏感的搜索中效率非常高,在那里可能不希望构建一个带有时间/日期公式的视图,也不想维护一个全文索引以使用 db.ftsearch 方法。还有,如果要执行查找的数据库不在您的控制之下,并且这些数据库没有进行全文索引,那么 db.search 可能是获取文档集合的惟一可用选项。
这里有一些图可以用来佐证 Pro 的上述观点。这些图显示了获取一个文档集合所需的时间。没有从文档中读取任何内容,也没有将任何内容写入文档。这是我们测试环境中的一个测试应用程序,所以这些数值并不代表真实环境下的情况。然而,各种方法之间的关系应该与您在自己的环境中发现的关系相一致。
在图 1 中,db.ftsearch 和 view.GetAllDocumentsByKey 之间并没有显著的不同,性能都是最好的。可以称作并列第一。比较接近的第三名应该是 view.GetAllEntriesByKey,同时 view.ftsearch 一开始的性能很好,但是当文档数目达到 40 或更多时,性能就很快地下降了。
图 1. 文档集合、优化的视图(最多 100 个文档)
在图 2 中,与图 1 相比惟一值得注意的不同是随着文档数目的增加,db.search 的性能也越来越好。在达到数据中文档数目的大约 5% 到 10% 时,db.search 和它前面的方法的速度一样快。正如我们在图 1 中所看到的,随着文档集合的增大,view.ftsearch 的性能越来越糟糕。
图 2. 文档集合、优化的视图(100 到 1,000 个文档)
在图 3 中,没有对视图进行优化以使结果位于顶部。也就是说,如果我们获取只有几个文档的集合,那么在我们的测试环境中,可以试图通过确保这些文档位于查询视图的顶部或底部来改变结果。在图 1 和 2 中,将这些文档放到了视图的顶部,但是在图 3 中,这些文档在底部。对于这三个方法,这并不重要(db.search、db.ftsearch 和 view.GetAllDocumentsByKey)。然而,对于 view.ftsearch 和 view.GetAllEntriesByKey,这一改变对性能产生了极大的影响。必须更改图 2 和图 3 中的刻度 —— Y 轴的最大时间刻度将不再是 1 秒,而是增加到 6 秒!
图 3. 文档集合、未优化的视图
结束语
只要可行,就使用 view.GetAllDocumentsByKey 来获取文档集合。简化查找视图可以与该方法同时使用,以使查找视图尽可能小并获得最大的效率。本文章系列的第 2 部分将提供一些进行该操作的技巧。
如果需要对 RTF 字段进行查找,或者是数据库已经进行了全文索引,那么 db.ftsearch 将具备极高的性能并且值得考虑使用。请确保返回的结果总是少于 5,000 个文档,或者是使用 Notes.ini 的参数 FT_MAX_SEARCH_RESULTS=n (其中 n 是能够返回的最大文档数)来保证不会因该限制而失去数据完整性。
这就是我们对 Notes/Domino 7 应用程序性能进行研究的第 1 部分。在第 2 部分中,我们将讨论如何构建高性能的视图。
如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。
现场直击|2021世界人工智能大会
直击5G创新地带,就在2021MWC上海
5G已至 转型当时——服务提供商如何把握转型的绝佳时机
寻找自己的Flag
华为开发者大会2020(Cloud)- 科技行者