#监视

0 关注者 · 18 帖子

监视是控制和管理软件应用程序的性能和可用性的过程。

文章 Michael Lei · 五月 12, 2021 11m read

InterSystems 数据平台和性能 - 第 5 部分 使用 SNMP 进行监控

在之前的帖子中,我展示了如何使用 pButtons 收集历史性能指标。 我首选 pButtons 是因为我知道它随每个数据平台实例(Ensemble、Caché、...)一起安装。 不过,还有其他方法可以实时收集、处理和显示 Caché 性能指标,以进行简单的监视,或进行更重要的并且复杂得多的运营分析和容量计划。 最常见的数据收集方法之一是使用 SNMP(简单网络管理协议)。

SNMP 是 Caché 向各种管理工具提供管理和监控信息的标准方式。 Caché 在线文档包含了 Caché 和 SNMP 之间接口的详细信息。 虽然 SNMP 应该可以直接与 Caché 配合工作,但仍有一些配置技巧和陷阱。 我经历了很多次错误的开始,并且在 InterSystems 其他同事的帮助下,才让 Caché 与操作系统 SNMP 主代理建立对话,所以我写了这篇帖子,希望您可以避免同样的痛苦。

在本帖中,我将介绍如何为 Red Hat Linux 上的 Caché 设置和配置 SNMP,您应该能够对其他 *nix 版本使用相同步骤。 我使用 Red Hat 写这篇文章是因为在 Linux 上进行设置更棘手一些;在 Windows 上,Caché 会自动安装一个 DLL 来与标准 Windows SNMP 服务连接,所以应该更容易配置。

3
2 456
文章 Lilian Huang · 九月 5, 2023 11m read

嗨,开发者们!

今天我想谈谈一个让我感到困难的话题。我相信你们中的很多人一定已经遇到过这种情况(所谓的“瓶颈”)。由于这是一个广泛的主题,因此本文将仅重点关注识别可能导致缓慢问题的传入 HTTP 请求。我还将向您提供我开发的一个小工具来帮助识别它们。

我们的软件变得越来越复杂,处理来自不同来源的大量请求,无论是前端还是第三方后端应用程序。为了确保最佳性能,必须有一个能够记录一些关键测量的日志系统,例如响应时间、global引用的数量以及每个 HTTP 响应执行的代码行数。作为工作的一部分,我参与了 EMR 软件的开发以及事件分析。由于用户负载主要来自 HTTP 请求(REST API 或 CSP 应用程序),因此在发生普遍缓慢问题时进行此类测量的需求变得显而易见。

0
0 295
文章 Tete Zhang · 七月 4, 2023 3m read

本文讨论了在使用或维护InterSystems产品中遇到问题时,试图确定问题时可能用到的思路和工具。

一般故障排除

确定问题发生的地点和时间

  • 问题是什么时候开始的?多久发生一次?
  • 问题首先出现在哪里?
  • 问题在什么条件下会被触发?

审查日志中的警告、错误和警报

以下日志可能包含有关该问题的有用信息。可以尝试在以下日志中寻找问题开始前后的警告或报错。

  • 检查 messages.log(IRIS)或者 cconsole.log(Caché and Ensemble)
    • 通过文件系统(<install-dir>/mgr/messages.log)访问messages.log文件,或者
    • 通过管理门户(系统操作>系统日志>Messages Log)访问文件内容
  • 检查production事件日志 (详细信息请参见文档
  • 查看应用程序错误日志 (详细信息请参见文档
  • 查看Web Gateway/CSP Gateway日志
  • 查看网络服务器(IIS/Apache)日志

检查实例是否可以访问足够的存储空间

  • 检查文件系统剩余空间(推荐设置操作系统层级的存储空间低告警)
  • 检查数据库剩余空间
    • 通过管理门户(系统操作>数据库>Freespace View)查看数据库文件内剩余空间百分比
  • 检查Journal日志空间

检查CPU活动

0
0 212
文章 Guangliang Zhang · 十月 21, 2022 6m read

cache数据库自身带有系统监控Portal界面,但需要运维人员定期主动查看才能获取监控信息。当系统故障发生时,容易出现由于没有及时获取故障信息而不能及时处理,从而导致造成的影响扩大。本文研究通过解析cache数据库控制台日志(cconsole.log)进行监控信息获取并主动推送微信或短信实现cache数据库主动实时监控。

cache数据库在运行时会将所有控制台消息包括一般消息、系统错误、某些操作系统错误和网络错误都会发送到控制台日志文件,通过操作员控制台工具从其他系统远程启动的作业的成功或失败等信息也会写入控制台日志,因此通过对控制台日志的解析即可获取所需要监控信息。具体步骤方法如下:

解析控制台日志

控制台日志默认存储在install-dir\mgr路径下。

根据cache版本不同,使用的读取方法也不同。对于cache2016版本以上,系统提供了EnsLib.SQL.Snapshot类,可以直接获取日志的行和列信息,非常方便。对于cache2010及以下版本则无此方法,需要使用%File文件读取方法。

3
2 640
文章 Michael Lei · 六月 1, 2022 1m read

InterSystems Production 监控是管理门户中的一个页面,用于显示当前运行的Production监控信息。我喜欢这个页面的样子,但这并不适合所有人。

2022年5月13日,我在开发者社区上看到这个帖子。

https://community.intersystems.com/post/creating-custom-monitoring-page

我同意马克的观点,Production监控很复杂。我想创建一个更漂亮干净的监控页面。

我开始着手制作一个利用类方法提供Production数据的 CSP(Cache Server Page)页面。

我与马克分享了我的第一次尝试。他根据自己的想法定制了这个页面。我喜欢他对页面的布局,使其在视觉上更有吸引力。我把他的设计整合到我的应用程序Production监控中。

我看到马克对业务服务的显示进行了过滤,只显示需要注意的服务。他在页面的底部添加了服务器的名称和它的镜像状态。

你可以在Open Exchange和当前的竞赛中找到我的Production监控器的应用。

https://openexchange.intersystems.com/package/production-monitor

0
0 142
公告 Michael Lei · 五月 3, 2022

nterSystems很高兴地宣布发布系统警报和监控(SAM)1.1版。

什么是SAM ?

SAM将IRIS基于标准的监控API日志监控与熟悉的行业标准工具如Grafana和Prometheus结合起来,为IRIS集群创建一个基本的监控和告警解决方案。

关于SAM的更多信息,请参阅《系统告警和监控指南》

SAM 1.1有什么新的内容?

你会注意到Grafana仪表盘图形的性能改进,特别是在处理大型数据集时。  如果你从SAM 1.0升级,你要确保你有足够的磁盘空间,因为SAM 1.1增加了额外的数据索引。

有关升级到SAM 1.1的更多信息,请参见发行说明

SAM 主页

SAM 实例详细页面

0
0 311
文章 Lele Yang · 八月 31, 2021 3m read

1 磁盘空间不足的常见情形
1)Journal所在磁盘空间不足。造成这种情况的原因有多种,比如,Mirror中备机未处于宕机状态,因此主机保留了Journal文件。
2)数据库所在磁盘空间不足。比如,集成平台上Ensemble/Health Connect/IRIS for Health的消息从未清除过,导致消息数据库DAT文件不断增大,直至将磁盘空间用尽。

我们可以看到以上两种情形下,Mirror都帮不上忙,也就是说一旦问题出现,主机和备机都没有办法立即承担起业务系统的运行,
第1)种,备机可能在故障出现前就已经宕机很长一段时间而没被发现,备机的数据很可能已经与主机严重不一致。实际上,主机之所以保留了大量的journal文件没有删除,就是为了让备机同步数据时使用。
第2)种,虽然配置了Mirror,但是消息主库正常情况下都是镜像库,在一个健康的Mirror中,备机和主机的镜像库数据保持同步,镜像库大小应当是相同的,假设主备机的硬件配置相同(这也是我们推荐的方式)那么磁盘空间不足在备机上同样存在。

2 应急措施 
切忌手动从文件系统中删除Journal文件!

以上两种情形我们都遇到过客户为了快速地腾出空间、恢复系统,第一时间手动从文件系统中将必要的Journal文件删除掉了,删除了这些Journal文件之后,尽管腾出了空间,但会造成系统无法启动,通常这个时候您会在日志文件中看到如下信息,

0
0 322
文章 Michael Lei · 七月 27, 2021 8m read

以下步骤展示如何显示 /api/monitor 服务提供的指标列表示例。

在上个帖子中,我概述了以 Prometheus 格式显示 IRIS 指标的服务。 该贴介绍了如何在容器中设置和运行 IRIS 预览版 2019.4,然后列出了指标。


本帖假定您已安装 Docker。 如果未安装,现在就为您的平台安装吧 :)


步骤 1. 下载并运行 docker 形式的 IRIS 预览版

按照预览发行版的下载说明下载预览版许可证密钥IRIS Docker 映像。 例如,我选择了 InterSystems IRIS for Health 2019.4

按照 Docker 容器中的 InterSystems 产品初见中的说明操作。 如果您熟悉容器,请跳转到标题为“下载 InterSystems IRIS Docker 映像”的部分。

以下终端输出说明了我用来加载 docker 映像的过程。 docker load 命令可能需要几分钟的时间才能运行;

$ pwd
/Users/myhome/Downloads/iris_2019.4

$ ls
InterSystems IRIS for Health (Container)_2019.4.0_Docker(Ubuntu)_12-31-2019.ISCkey  irishealth-2019.4.0.379.0-docker.tar

$ docker load -i irishealth-2019.4.0.379.0-docker.tar
762d8e1a6054: Loading layer [==================================================>]  91.39MB/91.39MB
e45cfbc98a50: Loading layer [==================================================>]  15.87kB/15.87kB
d60e01b37e74: Loading layer [==================================================>]  12.29kB/12.29kB
b57c79f4a9f3: Loading layer [==================================================>]  3.072kB/3.072kB
b11f1f11664d: Loading layer [==================================================>]  73.73MB/73.73MB
22202f62822e: Loading layer [==================================================>]  2.656GB/2.656GB
50457c8fa41f: Loading layer [==================================================>]   14.5MB/14.5MB
bc4f7221d76a: Loading layer [==================================================>]  2.048kB/2.048kB
4db3eda3ff8f: Loading layer [==================================================>]  1.491MB/1.491MB
Loaded image: intersystems/irishealth:2019.4.0.379.0

$ docker images
REPOSITORY                TAG                 IMAGE ID            CREATED             SIZE
intersystems/irishealth   2019.4.0.379.0      975a976ad1f4        3 weeks ago         2.83GB

为简单起见,将密钥文件复制将用于持久性存储的文件夹位置,并重命名为 iris.key

$ mkdir -p /Users/myhome/iris/20194
$ cp 'InterSystems IRIS for Health (Container)_2019.4.0_Docker(Ubuntu)_12-31-2019.ISCkey' /Users/myhome/iris/20194/iris.key

$ cd /Users/myhome/iris/20194
$ ls
iris.key

使用为持久性存储创建的文件夹启动 IRIS;

$  docker run --name iris --init --detach --publish 52773:52773 --volume `pwd`:/external intersystems/irishealth:2019.4.0.379.0 --key /external/iris.key

$ docker ps -a
CONTAINER ID        IMAGE                                    COMMAND                  CREATED              STATUS                        PORTS                      NAMES
009e52c121f0        intersystems/irishealth:2019.4.0.379.0   "/iris-main --key /e…"   About a minute ago   Up About a minute (healthy)   0.0.0.0:52773->52773/tcp   iris

很好! 您现在可以连接到正在运行的容器上的系统管理门户。 我使用的登录名/密码是 SuperUser/SYS;您第一次登录时会被提示更改密码。

导航到 Web 应用程序。 System > Security Management > Web Applications

您将看到一个 Web 应用程序:/api/monitor,这是用于显示 IRIS 指标的服务。

您无需执行任何操作即可返回指标,它直接可用。


步骤 2. 预览指标

在以后的帖子中,我们将使用 Prometheus 或 SAM _抓取_此端点,以设置的间隔收集指标。 但现在,让我们看一下为此实例返回的指标的完整列表。 例如,在 Linux 和 OSX 上,一个简单的方法是使用 curl 命令发出一个 HTTP GET。 例如,在我的(几乎不活动的)容器上,列表的开头为:

$ curl localhost:52773/api/monitor/metrics
:
:
iris_cpu_usage 0
iris_csp_activity{id="127.0.0.1:52773"} 56
iris_csp_actual_connections{id="127.0.0.1:52773"} 8
iris_csp_gateway_latency{id="127.0.0.1:52773"} .588
iris_csp_in_use_connections{id="127.0.0.1:52773"} 1
iris_csp_private_connections{id="127.0.0.1:52773"} 0
iris_csp_sessions 1
iris_cache_efficiency 35.565
:
:
等等。 在生产系统上,该列表可能非常长。  我在帖子末尾转储了完整列表。

另一个实用方法是使用 Postman 应用程序,但还有其他方法。 假定您已安装适合您的平台的 Postman,则可以发出 HTTP GET 并查看返回的指标。

总结

暂时就这么多内容。 在下个帖子中,我将从收集 Prometheus 中的数据开始,并查看一个 Grafana 仪表板示例。

预览容器中的完整列表

生产系统将提供更多指标。 从一些标签中可以看出,例如 {id="IRISLOCALDATA"} ,有一些指标与数据库有关,或者按进程类型对应于 CPU {id="CSPDMN"}

iris_cpu_pct{id="CSPDMN"} 0
iris_cpu_pct{id="CSPSRV"} 0
iris_cpu_pct{id="ECPWorker"} 0
iris_cpu_pct{id="GARCOL"} 0
iris_cpu_pct{id="JRNDMN"} 0
iris_cpu_pct{id="LICENSESRV"} 0
iris_cpu_pct{id="WDSLAVE"} 0
iris_cpu_pct{id="WRTDMN"} 0
iris_cpu_usage 0
iris_csp_activity{id="127.0.0.1:52773"} 57
iris_csp_actual_connections{id="127.0.0.1:52773"} 8
iris_csp_gateway_latency{id="127.0.0.1:52773"} .574
iris_csp_in_use_connections{id="127.0.0.1:52773"} 1
iris_csp_private_connections{id="127.0.0.1:52773"} 0
iris_csp_sessions 1
iris_cache_efficiency 35.850
iris_db_expansion_size_mb{id="ENSLIB"} 0
iris_db_expansion_size_mb{id="HSCUSTOM"} 0
iris_db_expansion_size_mb{id="HSLIB"} 0
iris_db_expansion_size_mb{id="HSSYS"} 0
iris_db_expansion_size_mb{id="IRISAUDIT"} 0
iris_db_expansion_size_mb{id="IRISLOCALDATA"} 0
iris_db_expansion_size_mb{id="IRISSYS"} 0
iris_db_expansion_size_mb{id="IRISTEMP"} 0
iris_db_free_space{id="ENSLIB"} .055
iris_db_free_space{id="HSCUSTOM"} 2.3
iris_db_free_space{id="HSLIB"} 113
iris_db_free_space{id="HSSYS"} 9.2
iris_db_free_space{id="IRISAUDIT"} .094
iris_db_free_space{id="IRISLOCALDATA"} .34
iris_db_free_space{id="IRISSYS"} 6.2
iris_db_free_space{id="IRISTEMP"} 20
iris_db_latency{id="ENSLIB"} 0.030
iris_db_latency{id="HSCUSTOM"} 0.146
iris_db_latency{id="HSLIB"} 0.027
iris_db_latency{id="HSSYS"} 0.018
iris_db_latency{id="IRISAUDIT"} 0.017
iris_db_latency{id="IRISSYS"} 0.020
iris_db_latency{id="IRISTEMP"} 0.021
iris_db_max_size_mb{id="ENSLIB"} 0
iris_db_max_size_mb{id="HSCUSTOM"} 0
iris_db_max_size_mb{id="HSLIB"} 0
iris_db_max_size_mb{id="HSSYS"} 0
iris_db_max_size_mb{id="IRISAUDIT"} 0
iris_db_max_size_mb{id="IRISLOCALDATA"} 0
iris_db_max_size_mb{id="IRISSYS"} 0
iris_db_max_size_mb{id="IRISTEMP"} 0
iris_db_size_mb{id="HSLIB",dir="/usr/irissys/mgr/hslib/"} 1321
iris_db_size_mb{id="HSSYS",dir="/usr/irissys/mgr/hssys/"} 21
iris_db_size_mb{id="ENSLIB",dir="/usr/irissys/mgr/enslib/"} 209
iris_db_size_mb{id="IRISSYS",dir="/usr/irissys/mgr/"} 113
iris_db_size_mb{id="HSCUSTOM",dir="/usr/irissys/mgr/HSCUSTOM/"} 11
iris_db_size_mb{id="IRISTEMP",dir="/usr/irissys/mgr/iristemp/"} 21
iris_db_size_mb{id="IRISAUDIT",dir="/usr/irissys/mgr/irisaudit/"} 1
iris_db_size_mb{id="IRISLOCALDATA",dir="/usr/irissys/mgr/irislocaldata/"} 1
iris_directory_space{id="HSLIB",dir="/usr/irissys/mgr/hslib/"} 53818
iris_directory_space{id="HSSYS",dir="/usr/irissys/mgr/hssys/"} 53818
iris_directory_space{id="ENSLIB",dir="/usr/irissys/mgr/enslib/"} 53818
iris_directory_space{id="IRISSYS",dir="/usr/irissys/mgr/"} 53818
iris_directory_space{id="HSCUSTOM",dir="/usr/irissys/mgr/HSCUSTOM/"} 53818
iris_directory_space{id="IRISTEMP",dir="/usr/irissys/mgr/iristemp/"} 53818
iris_directory_space{id="IRISAUDIT",dir="/usr/irissys/mgr/irisaudit/"} 53818
iris_disk_percent_full{id="HSLIB",dir="/usr/irissys/mgr/hslib/"} 10.03
iris_disk_percent_full{id="HSSYS",dir="/usr/irissys/mgr/hssys/"} 10.03
iris_disk_percent_full{id="ENSLIB",dir="/usr/irissys/mgr/enslib/"} 10.03
iris_disk_percent_full{id="IRISSYS",dir="/usr/irissys/mgr/"} 10.03
iris_disk_percent_full{id="HSCUSTOM",dir="/usr/irissys/mgr/HSCUSTOM/"} 10.03
iris_disk_percent_full{id="IRISTEMP",dir="/usr/irissys/mgr/iristemp/"} 10.03
iris_disk_percent_full{id="IRISAUDIT",dir="/usr/irissys/mgr/irisaudit/"} 10.03
iris_ecp_conn 0
iris_ecp_conn_max 2
iris_ecp_connections 0
iris_ecp_latency 0
iris_ecps_conn 0
iris_ecps_conn_max 1
iris_glo_a_seize_per_sec 0
iris_glo_n_seize_per_sec 0
iris_glo_ref_per_sec 7
iris_glo_ref_rem_per_sec 0
iris_glo_seize_per_sec 0
iris_glo_update_per_sec 2
iris_glo_update_rem_per_sec 0
iris_journal_size 2496
iris_journal_space 50751.18
iris_jrn_block_per_sec 0
iris_jrn_entry_per_sec 0
iris_jrn_free_space{id="WIJ",dir="default"} 50751.18
iris_jrn_free_space{id="primary",dir="/usr/irissys/mgr/journal/"} 50751.18
iris_jrn_free_space{id="secondary",dir="/usr/irissys/mgr/journal/"} 50751.18
iris_jrn_size{id="WIJ"} 100
iris_jrn_size{id="primary"} 2
iris_jrn_size{id="secondary"} 0
iris_license_available 31
iris_license_consumed 1
iris_license_percent_used 3
iris_log_reads_per_sec 5
iris_obj_a_seize_per_sec 0
iris_obj_del_per_sec 0
iris_obj_hit_per_sec 2
iris_obj_load_per_sec 0
iris_obj_miss_per_sec 0
iris_obj_new_per_sec 0
iris_obj_seize_per_sec 0
iris_page_space_per_cent_used 0
iris_phys_mem_per_cent_used 95
iris_phys_reads_per_sec 0
iris_phys_writes_per_sec 0
iris_process_count 29
iris_rtn_a_seize_per_sec 0
iris_rtn_call_local_per_sec 10
iris_rtn_call_miss_per_sec 0
iris_rtn_call_remote_per_sec 0
iris_rtn_load_per_sec 0
iris_rtn_load_rem_per_sec 0
iris_rtn_seize_per_sec 0
iris_sam_get_db_sensors_seconds .000838
iris_sam_get_jrn_sensors_seconds .001024
iris_system_alerts 0
iris_system_alerts_new 0
iris_system_state 0
iris_trans_open_count 0
iris_trans_open_secs 0
iris_trans_open_secs_max 0
iris_wd_buffer_redirty 0
iris_wd_buffer_write 0
iris_wd_cycle_time 0
iris_wd_proc_in_global 0
iris_wd_size_write 0
iris_wd_sleep 10002
iris_wd_temp_queue 42
iris_wd_temp_write 0
iris_wdwij_time 0
iris_wd_write_time 0
iris_wij_writes_per_sec 0

0
0 318
文章 Michael Lei · 六月 23, 2021 3m read

注(2019 年 6 月):许多内容发生了变化,最新的详细信息请参见此处 注(2018 年 9 月):自本帖首次发布以来,内容已经有了很大改动,我建议使用 Docker 容器版本,以容器形式运行的项目以及详细信息仍然在 GitHub 的同一个地址发布,您可以下载、运行并根据需要进行修改。

与客户合作进行性能评估、容量规划和故障排除时,我经常解包和查看来自 pButtons 的 Caché 和操作系统指标。 我不久前发布了一个帖子,介绍了一个用来解包 pButtons 指标的实用工具(该实用工具使用 unix shell、perl 和 awk 脚本编写),而不是费力地浏览 html 文件,再将需要绘制的部分剪切并粘贴到 excel 中。 虽然这是一个有用的省时工具,但还不够完善... 我还使用脚本自动绘制指标图表,以便快速查看并包含在报告中。 但是,这些绘图脚本不容易维护,并且当需要站点特定的配置(例如 iostat 或 Windows perfmon 的磁盘列表)时会变得特别混乱,所以我从未公开发布过绘图实用工具。 不过我现在可以很高兴地说,已经有了简单得多的解决方案。

当我与 Fabian 一起在客户站点查看系统性能时,有了意外发现,他向我展示了使用实用的 Python 绘图模块所做的工作。 这是一个比我使用的脚本更灵活、更容易维护的解决方案。 集成 Python 模块进行文件管理和绘制图表的简便性,包括可以分享的交互式 html,意味着输出可以有更大用处。 以 Fabian 的帖子为基础,我编写了 Yape,旨在快速简单地提取客户的多种格式的 pButtons 文件,然后绘制图表。 该项目已在 GitHub 上发布,您可以下载、运行并根据需要进行修改。

概述

目前,此过程有_两个_步骤。

步骤 1. extract_pButtons.py

从 pButtons 提取感兴趣的部分并写入到 .csv 文件,以便使用 Excel 打开或使用 graph_pButtons.py 进行绘图处理。

步骤 2. graph_pButtons.py

绘制步骤 1 中创建的文件的图表。 目前,输出可以是 .png 形式的线形图或点阵图,也可以是带有平移、缩放、打印等选项的交互式 .html

GitHub 上的 Readme.md 详细介绍了如何设置和运行这两个 python 脚本,并且将是最新的参考。

其他说明

例如:使用向输出和输入目录添加前缀的选项,可以轻松遍历包含一组(例如一个星期)pButtons html 文件的目录,并针对每个 pButtons 文件都输出到一个单独目录。

for i in `ls *.html`; do ./extract_pButtons.py $i -p ${i}_; done

for i in `ls *.html`; do ./graph_pButtons.py ./${i}_metrics -p ${i}_; done

在短期内,当我继续撰写有关 Caché 容量规划和性能的系列文章时,我将使用由这些实用工具创建的图表。

我已经在 OSX 上进行了测试,但没有在 Windows 上测试。 您应该能够在 Windows 上安装和运行 Python,请留下您在 Windows 下的经验反馈。 例如,我猜想必须对文件路径斜杠进行更改。

注:直到几周前,我都没有用 Python 编写过任何东西,所以如果您是 Python 专家,那么代码中可能会有一些内容并不是最佳做法。 但是,我几乎每天都使用这些脚本,因此我将继续进行改进。 我希望我的 Python 技能会有所提高 — 但是如果您看到一些应该纠正的地方,请随意“教导”我!

如果您发现这些脚本有用,请告诉我,并不时回来看看以获取新功能和更新。

0
0 198
文章 Nicky Zhu · 五月 20, 2021 7m read

在上一篇文章《互操作消息统一管理系列:Message Bank》中,我们了解到在Message Bank中,消息均以半结构化(XML)或非结构化(Stream)的形式保存,因此无法与客户端的结构化消息一样,直接支持基于索引的检索。为此,需要在Message Bank中定义Search Table以支持查询。关于Search Table的定义和作用,请查阅https://docs.intersystems.com/healthconnectlatest/csp/docbook/DocBook.UI.Page.cls?KEY=EEDI_search_tables。

一. 在Message Bank中查询消息的特殊之处

大家如果使用过消息查看器,则能够了解IRIS自动持久化消息并提供界面让大家能够根据消息头中(如发生事件、来源、目标等)或消息体中(如患者姓名、诊断名称等消息的具体属性)来查询消息。 而在Message Bank上进行查询时,如果直接使用消息查看器,能够查询的是Message Bank的Production中传输的消息,而若不是在源系统中出现的消息,这一点一定不要混淆。 Message Bank提供了消息仓库查看器供大家查询源系统中的消息 image

查询界面尽管在风格与功能上与消息查看器非常相似,但查询的目标是在Message Bank中转储的消息(存储格式为虚拟文档或字符流,见上一篇文章)。因此,查询索引与原来的消息截然不同,需要单独创建。其中,虚拟文档在IRIS中有原生支持,我们先来看自定义消息类型的处理过程。

先看这样一个案例。

二. 测试系统

假设我们有一个REST接口实现的添加患者业务,如下所示: image

实现了REST接口,接收如下格式的POST请求,在该实例数据库中创建患者: image

注意,从外部JSON传入的报文,在经过IRIS处理的过程中,会被转换为XML格式的消息,如下: image

在被转储到Message Bank后,以同样结构的XML字符流存储 image

三. 在消息转储过程中创建索引

Message Bank中转储的消息默认是没有索引的,需要在消息转储过程中创建。

通过回调干预消息转储过程

Message Bank的Production中的服务Ens.Enterprise.MsgBank.TCPService提供了回调函数入口,使用户可以在消息转储过程中执行自定义的逻辑实现需求。因此,可用于触发对Search Table索引的建立过程。 要定义回调函数,则需要继承类Ens.Enterprise.MsgBank.BankHelperClass,并实现方法OnBankMsg,如下所示:

Class MessageBank.MsgSerializationHelper Extends Ens.Enterprise.MsgBank.BankHelperClass [ Language = objectscript ]
{

/// 参数IndexClassList用于指定将被该回调函数索引的(客户端)消息类
Parameter IndexClassList = "LIS.REST.MSG.PatientReq";

/// 消息签名不能更改
ClassMethod OnBankMsg(pHeader As Ens.Enterprise.MsgBank.MessageHeader, pFullHeaderID As %String, pBody As %RegisteredObject = "", pFullBodyID As %String, pService As Ens.Enterprise.MsgBank.TCPService) As %Status [ Language = objectscript ]
{
		Set tSC = $$$OK
	do
	{
		set clientBodyClassName = pHeader.ClientBodyClassName
		
		/// 如果消息类型在本类的关注列表中,则进行后续处理;否则忽略,只被转储
if ..#IndexClassList[clientBodyClassName
		{
			/// 使用服务实例将Message Bank消息(包含客户端消息信息)传递给后续组件处理
			do pService.SendRequestAsync("MessageBank.Process.ReceiveBankMsgBPL", pHeader)
		}
	} while 0
		
	Quit $$$OK
}

}

注意,尽管整个建立Search Table的过程可以在服务中完成,但由于服务不能开启多进程,对于构建索引这样的高延迟任务,容易变成性能瓶颈。因此,强烈建议将消息转发给BP和BO,并启用多进程处理提高处理速度。

定义好回调函数和Helper类执行,在BS中引用即可,如下: image

在Business Process中分发建立Search Table 建立索引的过程需要应用特定的Search Table类实现,因此通过Business Service将消息分发到Business Process后,则可通过BP根据消息的类型再调用特定的Search Table实现,如下: image

即根据消息类型的不同,使用不同的逻辑进行索引。其中,对于本例的消息类型LIS.REST.MSG.PatientReq image

在switch分支中判定后即可调用对应的代码段处理。 image

在该段代码中,我们需要使用Message Bank消息(该BP的request)中记录的MessageBodyId,对于在源系统中的结构化消息,在Message Bank中被记录为流,该Id即为流记录的Id。根据Id加载流对象后,将该流对象通过IndexDoc方法传递给这类消息对象专用的Search Table实现类完成索引的建立。

Search Table类的实现

该类需要继承Ens.CustomSearchTable并实现OnIndexDoc回调方法,如下:

Class MessageBank.SearchTable.Client.LISReqTable Extends Ens.CustomSearchTable [ Language = objectscript ]
{

///在Search Table中对要建立索引的字段建模,并对该字段建立索引,在查询时即可套用
Property FullName As %String(MAXLEN = 36);

Index IdxFullName On FullName;

///特别提醒,在Message Bank中转储的结构化消息类型会变成字符流,因此,我们是在对字符流建立索引,而不是对源系统的消息类型建立索引
Parameter DOCCLASS = "%Stream.GlobalCharacter";

///建立索引的回调方法
ClassMethod OnIndexDoc(pDocObj As %Persistent, ByRef pSearchTable As MessageBank.SearchTable.Client.LISReqTable) As %Status
{
	set st = $$$OK
	do
	{
		set text = pDocObj.Read($$$MaxLocalLength)
		set text = $ZSTRIP(text, "*C",, $C(9,10,13,133))
///读取字符流建立虚拟文档对象
		set vDoc = ##class(EnsLib.EDI.XML.Document).ImportFromString(text, .st) quit:$$$ISERR(st)
///从虚拟文档对象获得所需的属性值并为Search Table对象赋值
		set pSearchTable.FullName = vDoc.GetValueAt("/PatientReq/FullName")
		
	} while 0
	
	if $$$ISERR(st) $$$LOGWARNING("Failed to index message LIS.REST.MSG.PatientReq:"_pDocObj.%Id()_" "_st)
	
	quit $$$OK
}

}

应当注意的是,使用Search Table支持查询,则意味着查询的目标是Search Table对象而不是直接在Message Bank转储的消息上进行查询。平台的查询功能会负责维护查询逻辑,在Search Table上执行查询并关联到消息上去。 因此,Search Table的属性可以使用消息中的属性组合,大家也可以思考和改进本例实现这样一个场景,源系统中不传FullName全名,代之以分在两个属性中的姓和名。但在Message Bank中却可以在Search Table上建立全名FullName并建立索引,使用户可以通过全名查询消息。

还应当注意到的是,在解析字符流获取所需的属性值时,用户可以选择使用虚拟文档处理,如果能够获得原始消息的类定义,也可以将字符流转换为消息对象再取值。考虑到在实际的生产环境下在Message Bank并不能确保获取源消息定义,采用虚拟文档更为通用且不受类型限制,推荐大家使用。

四. 实现效果

定义好上述代码后启动Message Bank的Production,并测试源系统的接口: image

此时查看Message Bank的production上的消息: image

可看到在Message Bank有新消息触发了处理流程 通过SQL查询Search Table中的记录 image

则可看到最后一行中记录的全名 再转到消息仓库查看器,查询时选择查询表域,选择好对应的SearchTable类型,选择我们刚才使用的全名Biden image

则可查询到消息和对应的Message Trace image

对虚拟文档的处理过程

源系统的消息类型如为虚拟文档,例如EnsLib.EDI.XML.Document,则Message Bank的转储消息类型也是EnsLib.EDI.XML.Document,此时的处理流程与上述流程一致,但处理细节上会简单一些。体现在

MessageBank消息对象的获取

image

如上图所示,不直接使用字符流,通过Id直接获得虚拟文档对象即可。

Search Table的定义

Class MessageBank.SearchTable.DocSearchTable Extends EnsLib.EDI.XML.SearchTable [ Language = objectscript ]
{

XData SearchSpec [ XMLNamespace = "http://www.intersystems.com/EnsSearchTable" ]
{
<Items>
   <Item DocType="example:shiporder" PropName="OrderPerson" PropType="String:CaseSensitive">{orderperson}</Item>
   <Item DocType=":root" PropName="d_e" PropType="String:CaseSensitive">{/root/d/e}</Item>
   <Item DocType=":root" PropName="li_la_c" PropType="String:CaseSensitive">{/root/lis/li}</Item>
</Items>
}

}

如上所示,EnsLib.EDI.XML.Document有系统原生的Search Table模版EnsLib.EDI.XML.SearchTable,继承该类后在XData中定义好Search Table的属性和取值路径(在消息XML中的路径)即可。 关于EnsLib.EDI.XML.Document的Search Table,可通过https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=EEDI_search_tables获取更多信息。

0
0 283
文章 Nicky Zhu · 五月 20, 2021 7m read

一. 企业信息库简介

企业信息库(MessageBank)是一个可选的远程归档设施,可以从多个来自不同实例的互操作性Production中收集信息、事件日志项目和搜索表项。如下图所示: image

这套环境由两种角色的实例构成: 企业信息库服务器,它本身也是一个Production,完全由Message Bank服务组成,接收来自任何数量的客户Production提交的消息、日志等。

客户端Operation(Message Bank Operation),将其添加到一个正在运行的Production中,并用企业信息库服务器的地址进行配置。如连接通畅,消息和日志即可自动转发到Message Bank并在其中存储。

为了使你能方便地看到信息库中的信息,InterSystems IRIS®提供了以下附加选项。

对于企业信息库实例,管理门户自动包括企业监控器页面,在那里你可以监控客户端Production的状态,浏览消息库,并对被监控客户的消息进行检索。

对于每个客户端实例,你在消息库实例中配置一个到企业监控器的链接。

如下所示: image

二. 常见应用场景

消息归档

在使用IRIS互操作性时,对于生产环境,为保障其有充足的磁盘空间和即时查询的效率,通常会采用消息和日志过期策略。在生产环境中只保留近期(如一个月)的信息以备回溯,过期数据将定期被清除。因此,如果有长期保留消息(如在生产环境清除周期之外还需要更长时间的回溯)的需求,则可以通过Message Bank对消息和日志进行长期保存。

企业消息仓库

对于集成规模较大,集成业务较多的大型企业和集团(如大型医院、医联体、医共体),往往会采用多套互操作性实例支撑数据交换和集成业务。在这种环境下,可以通过Message Bank汇聚和存储整个企业环境下的所有互操作消息和日志,为业务集中监控、跨实例业务故障分析等工作创造条件。

消息和日志再利用

理想条件下,实施互操作性项目之后,消息和日志中就会包括大量的业务数据,典型的包括下达的医嘱、患者信息、医疗记录等。通过对Message Bank中的数据进行分析和挖掘,能够获得有价值的业务信息。

接下来我们会为大家介绍Message Bank的搭建过程。

三. 搭建Message Bank

创建Message Bank 命名空间

在生产所用的实例之外,我们需要使用一台独立的实例用于安装和配置Message Bank(实例安装过程和License激活过程从略,请查看安装文档或联系您的支持工程师)。 在该实例上,创建一个命名空间安装Message Bank,如下所示: image

由于Message Bank本质上由Production实现,因此创建命名空间时要选上对互操作Producation的支持。

InterSystems为大家提供了可以套用的Production模版。因此,请按照以下步骤创建Production:

在刚才创建的命名空间MessageStore下创建类MessageBank.BankProduction,继承Ens.Enterprise.MsgBank.Production并将Ens.Enterprise.MsgBank.Production中的XData代码块拷贝到新建的类中,如下所示: image

保存和编译该类,并在Interoperability菜单中加载该Production。 image

其中已部署了两个服务: MsgBankService:该服务通过TCP连接从其他Production接收消息 注意该Service默认使用9192端口与其他客户端通信。 image

MonitorService:该服务收集其他实例的其他Production的运行状态

此时,这个Production已经具备了从其他实例的Production收集消息和事件信息的能力,可直接启动。当然,我们还需要配置与客户端的连接。

为客户端Production添加消息转发Operation

假设我们已经有一个可运行的的如下所示的Production image

注意这个Production与Message Bank不在同一个实例上。 image

这个Production接收XML格式的报文并根据报文类型转发到不同的BO。

要将这个Production加入Message Bank,则需要对该客户端Production添加Business Operation Ens.Enterprise.MsgBankOperation。 image

对于该Operation,需要指定要连接的Message Bank的IP地址和端口。 image

同时,建议开启这个Operation的“启用存档”开关,保证在Message Bank临时故障时挂起消息,在故障恢复后还能捕捉到故障期间的消息和日志。 配置完成后启用该Operation。

在Message Bank中加入客户端信息

上述连接建立后,客户端和Message Bank间的连接已建立,还需要配置Message Bank和客户端Production之间的程序信息(相当于注册)才能正常工作。

添加客户端连接凭据

image

Message Bank需要通过Web请求访问客户端信息,因此,需要配置客户端凭据,即可通过管理门户访问客户端Production的用户名和密码(对访问权限的设计和配置,可参见我们之前的文章:IRIS中的权限管理)

在Message Bank上配置客户端信息

在Message Bank中的Interoperability菜单中找到“企业系统”项 image

在操作页面上通过“新建连接” image

新建连接添加客户端信息。 image

注意其中的服务Web应用路径为该客户端实例上Production所在的命名空间的Web Application根路径,并引用之前填写的凭据。 如配置正确,可通过企业监视器查看连接状态 image

连接成功的状态如下: image

在客户端上添加Message Bank连接信息(可选步骤)

如果需要在客户端上通过链接查看消息仓库的信息,则可以配置链接。 在客户端上,在被采集的Production所在的命名空间的Interoerability菜单中“消息仓库链接”配置 image

输入Message Bank所在的IP、端口和Production所在的命名空间,保存并“开始”即可跳转到Messsage Bank的企业监视器。 image

需要注意的是,该配置固定采用了/csp/[namespace]为Message Bank的Web Application路径,而在Message Bank实例上,这个Web Application默认的路径是/csp/healthshare/messagestorage。可通过在Message Bank上添加一个Web Application,拷贝/csp/healthshare/messagestorage的配置。 image

四. Message Bank的实施效果

测试消息

在客户端的Production中触发任意流程产生消息,如下所示: image

此时通过Message Bank中的“消息仓库查看器”即可查询存储在消息仓库中的消息 image

如下: image

可以注意到该消息已被同步到消息仓库。

需要注意,使用“消息仓库查看器”时,查询的是在Message Bank中存储的消息数据,使用在Message Bank上定义的Search Table或索引进行查询;如果通过“企业消息查看器”查询,则是链接到客户端的消息查看器查询,应用的是在客户端上定义的索引。

消息的存储

根据在源系统的消息类型的不同,传递到Message Bank后会以不同的形式保存消息。

虚拟文档

对于HL7 V2等标准消息或基于XML虚拟文档的消息,在Message Bank这一侧也同样以虚拟文档的形式保存。 image

特别注意其中的如下属性: MessageBodyClassName:该类型为消息在Message Bank侧持久化的类型。 ClientBodyClassName:该类型为消息在客户端侧持久化的类型。 在本例中可以看到,客户端通过EnsLib.EDI.XML.Document类型传递的消息,在Message Bank中也是通过EnsLib.EDI.XML.Document保存。 MessageBodyId:消息在Message Bank中的物理主键 ClientBodyId:客户端侧持久化消息的物理主键 ClientSessionId:客户端会话Id

结构化消息

对于基于Ens.Request等持久化类型的消息,在Message Bank这一侧则默认使用字符流来保存。 例如,对于如下的客户端结构化消息传输 image

在Message Bank中的保存形式为: image

可见: MessageBodyClassName:消息在Message Bank中以%Stream.GlobalCharacter即字符流进行保存

因此,无论是保存为EnsLib.EDI.XML.Document或是%Stream.GlobalCharacter,在Message Bank中保存的消息本身都缺乏足够的结构化特征和索引以支持对消息体的检索,我们会在下一篇教程《互操作消息统一管理系列:SearchTable加速检索》中介绍如何通过构建Search Table来检索这些消息。

对于Message Bank相关的内容,可参见: https://docs.intersystems.com/healthconnect20201/csp/docbook/DocBook.UI.Page.cls?KEY=EGDV_message_bank 也欢迎与我们联系获得更详细的信息。

0
0 363
文章 Claire Zheng · 一月 21, 2021 3m read

大家好!

我想跟大家分享一个个人项目,该项目始于工作中的一个简单需求:“能否知道我们使用了多少个Caché许可证?”

在阅读社区的其他文章时,我发现了一篇David Loveluck写的非常棒的文章:APM——使用Caché History Monitor

我根据David的这篇文章,开始使用Caché History Monitor并显示所有这些信息。

在面临“选择哪种很酷的技术”这个问题时,我决定使用简单而强大的CSP,这样我的客户可以认识到Caché不仅仅是MUMPS/终端。

在创建了页面以显示许可、数据库增长和CSP会话的历史记录后,我决定为System Dashboard和进程页面创建一个新设计。

我的Caché实例运行得良好。

但是,如果使用IRIS呢?

0
0 139
文章 Claire Zheng · 一月 20, 2021 5m read

我们不必等待SAM发布才开始规划和试用该API来监控IRIS实例。在以后的文章中,我将更深入地探讨可用的指标及其意义,并提供一些交互式仪表板的示例。首先,我将介绍一下相关背景和一些问题及答案。

IRIS(和Caché)一直在收集自身及其运行平台的数十个指标。收集这些指标来监控Caché和IRIS的方法向来有很多。我发现,很少有安装软件使用IRIS和Caché的内置解决方案。譬如,History Monitor作为性能和系统使用指标的历史数据库,已经推出很长时间了,但它没有简便方法可实时显示这些指标和仪表系统。

IRIS平台解决方案(以及整个业界)正在从仅在一些本地实例上运行的单体式应用程序过渡到“随处”部署的分布式解决方案。在许多用例中,原有的IRIS监控方案并不适用于这些新的模式。InterSystems没有做重复工作,而是将目光投向当前流行的、经过验证的监控和告警开源解决方案。

0
0 397
文章 Nicky Zhu · 一月 18, 2021 4m read

在最近的项目里,多方同时连接同一个数据库并执行增删改查等各项数据操作。研发人员不时发现一些数据在不合规的情况下被新增甚至删除。因此,在实际工作中会有监控数据操作以便识别和处理异常操作的需求。本文将以监控和识别删除操作为例,介绍如何通过IRIS的审计功能实现对数据操作的监控和查询。

注意事项

在应用审计功能之前,必须注意的是:

  1. 开启审计功能会事无巨细地记录每一条对应的操作(如被执行的SQL),因此对于存储空间的需求将急剧增加。举例而言,仅开启对XDBCStatement的监控后,对于一张只由5个简单(整型,VARCHAR型)字段构成的表中插入100万条记录,在Audit数据库中将占用300~400MB的空间。在因业务所需确实需要开启审计功能时,必须预先分配更多磁盘空间给IRIS Audit数据库,并在审计功能开启期间定时巡检磁盘空间,避免因日志占满磁盘导致其他数据无法写入引发系统挂起的故障。
  2. 在研发环境中多人、多单位需要连接数据库时,应为不同的开发者和数据来源分配独立的数据库账户和权限,避免多人共用超级账户,导致数据异常时难以追踪异常操作究竟从何而来。也就意味着为不同角色的开发、测试、用户等参与者开启独立的用户,分配各自所需的的数据库权限以及管理数据库账户这样一系列项目正常运行所依赖的实践并不能被审计功能所替代。在项目进展过程中,您更希望见到的,一定不是出现问题后再来跟踪问题和耽误工期,而是通过良好的协作规程和研发习惯减少非技术问题出现的概率。

IRIS中的审计功能

IRIS提供了丰富的数据库审计功能,用于记录从系统权限变更到数据删除的各类操作,用户可通过我们的官方文档查看系统支持的各类审计事件。 对于SQL操作而言,可用到的审计事件包括三大类:

  • %System/%SQL/DynamicStatement 可捕获通过动态SQL执行的SQL操作
  • %System/%SQL/EmbeddedStatement 可捕获通过嵌入式SQL执行的SQL操作
  • %System/%SQL/XDBCStatement 可捕获通过第三方(JDBC/ODBC)SQL连接执行的SQL操作

开启数据库审计

  1. 由于默认安装时IRISAUDIT数据库通常位于IRIS安装目录,空间有限,因此在需要开启审计时,应先将IRISAUDIT数据库从系统安装目录迁移到空间更充裕的数据库目录

  2. 在System Administration -> Security -> Auditing -> Configure System Events下,开启对%System/%SQL/XDBCStatement的监控,如下图 image

  3. 预先配置好删除AuditLog的任务,以便在AuditLog过大时清除数据 系统虽然自带定时删除Audit数据库的任务,但其默认的触发条件为在Journal发生切换时才执行,因此不适用于需要扩展监控范围导致审计库将剧增的情况,应配置额外的任务便于随时执行。 image 并将其设置为按需启动 image

  4. 待可疑操作发生时,使用SQL工具或portal在%SYS命名空间下运行如下SQL:

SELECTID, AuditIndex, Authentication, CSPSessionID, ClientExecutableName, ClientIPAddress, Description, Event, EventData, EventSource, EventType, GroupName, JobId, JobNumber, Namespace, OSUsername, Pid, Roles, RoutineSpec, Status, SystemID, UTCTimeStamp, UserInfo, UsernameFROM %SYS.Auditwhere Description = 'SQL DELETE Statement'order by UTCTimeStamp desc

即可找到最近的删除操作。通过Truncate table或delete语句执行的操作均可由该日志捕获到。如需监控insert或update等操作,在上述Description字段的选择上加入对insert或update语句的筛选即可。 通过这些日志,可以查看到操作发生的时间,来源IP和数据库用户等信息,如下所示: image

  1. 必须再次提醒各位,一旦开启对语句的监控,Audit数据库会快速增长,需要每日甚至每日多次巡检,确保磁盘空间不会被占满导致系统崩溃。 一旦发现Audit所在磁盘占有量过大(例如大于80%)或Audit库本身的占用过大(例如大于20G),即应运行步骤3中 配置的任务,然后对Audit库进行压缩和截断操作释放空间。
0
0 410
文章 Hao Ma · 一月 10, 2021 15m read

自 Caché 2017 以后,SQL 引擎包含了一些新的统计信息。 这些统计信息记录了执行查询的次数以及运行查询所花费的时间。

对于想要对包含许多 SQL 语句的应用程序的性能进行监控和尝试优化的人来说,这是一座宝库,但访问数据并不像一些人希望的那么容易。

本文和相关的示例代码说明了如何使用这些信息,以及如何例行提取每日统计信息的摘要,并保存应用程序的 SQL 性能的历史记录。

记录了什么?

每次执行 SQL 语句时,都记录花费的时间。 这是非常轻量的操作,无法关闭。 为了最大程度地降低开销,统计信息保留在内存中并定期写入磁盘。 数据包括一天中执行查询的次数以及所花费的平均时间和总时间。

数据不会立即写入磁盘,并且在写入之后,统计信息将由“更新 SQL 查询统计信息”任务更新,该任务通常计划为每小时运行一次。 该任务可以手动触发,但是如果你希望在测试查询时实时查看统计信息,则整个过程需要一点耐心。

警告:在 InterSystems IRIS 2019 及更早版本中,不会针对已使用 %Studio.Project:Deploy 机制部署类或例程中的嵌入式 SQL 收集这些统计信息。 示例代码不会有任何中断,但这可能会使你产生误导(我被误导过),让你以为一切正常,因为没有查询显示为高开销。

如何查看信息?

你可以在管理门户中查看查询列表。 转到 SQL 页面,点击“SQL 语句”选项卡。 对于你正在运行并查看的新查询,这种方式很好;但是如果有数千条查询正在运行,则可能变得难以管理。

另一种方法是使用 SQL 搜索查询。 信息存储在 INFORMATION_SCHEMA 模式的表中。 该模式含有大量表,我在本文的最后附上了一些 SQL 查询示例。

何时删除统计信息?

每次重新编辑查询时会删除其数据。 因此对于动态查询,这可能意味着清除缓存的查询时。 对于嵌入式 SQL,则意味着重新编译在其中嵌入 SQL 的类或例程时。

在活跃的站点上,可以合理预期统计信息将保存超过一天,但是存放统计信息的表不能用作运行报告或长期分析的长期参考源。

如何汇总信息?

我建议每天晚上将数据提取到永久表中,这些表在生成性能报告时更易于使用。 如果在白天编译类,可能会丢失一些信息,但这不太可能对慢速查询的分析产生任何实际影响。

下面的代码示例说明了如何将每个查询的统计信息提取到每日汇总中。 它包括三个简短的类:

  • 一个应在每晚运行的任务。
  • DRL.MonitorSQL 是主类,用于从 INFORMATION_SCHEMA 表提取数据并存储。
  • 第三个类 DRL.MonitorSQLText 是一个优化类,它存储一次(可能很长的)查询文本,并且只将查询的哈希存储在每天的统计信息中。
  • 示例说明

    该任务提取前一天的信息,因此应安排在午夜后不久执行。

    你可以导出更多历史数据,只要其存在。 要提取过去 120 天的数据

    Do ##class(DRL.MonitorSQL).Capture($h-120,$h-1)

    该示例代码直接读取全局 ^rIndex,因为最早版本的统计信息未将日期公开给 SQL。

    我所包括的变体将循环实例中的所有命名空间,但这并不总是合适的。

    如何查询已提取的数据

    提取数据后,您可以通过运行以下语句查找最繁重的查询

    SELECT top 20S.RunDate,S.RoutineName,S.TotalHits,S.SumpTIme,S.Hash,t.QueryTextfrom DRL.MonitorSQL Sleft join DRL.MonitorSQLText T on S.Hash=T.Hashwhere RunDate='08/25/2019'order by SumpTime desc

     

    此外,如果选择了开销大的查询的哈希,可以通过以下语句查看该查询的历史记录

    SELECT S.RunDate,S.RoutineName,S.TotalHits,S.SumpTIme,S.Hash,t.QueryTextfrom DRL.MonitorSQL Sleft join DRL.MonitorSQLText T on S.Hash=T.Hashwhere S.Hash='CgOlfRw7pGL4tYbiijYznQ84kmQ='order by RunDate

     

    今年早些时候,我获取了一个活跃站点的数据,然后查看了开销最大的查询。 有一个查询的平均时间不到 6 秒,但每天被调用 14000 次,加起来每天消耗的时间将近 24 小时。 实际上,一个核心完全被这个查询占用。 更糟糕的是,第二个查询要花一个小时,它是第一个查询的变体。

    <td>
      例程名称
    </td>
    
    <td>
      总命中次数
    </td>
    
    <td>
      总时间
    </td>
    
    <td>
      哈希
    </td>
    
    <td>
      查询文本(有节略)
    </td>
    
    <td>
       
    </td>
    
    <td>
      14,576
    </td>
    
    <td>
      85,094
    </td>
    
    <td>
      5xDSguu4PvK04se2pPiOexeh6aE=
    </td>
    
    <td>
      DECLARE C CURSOR FOR SELECT * INTO :%col(1) , :%col(2) , :%col(3) , :%col(4)  …
    </td>
    
    <td>
       
    </td>
    
    <td>
      15,552
    </td>
    
    <td>
      3,326
    </td>
    
    <td>
      rCQX+CKPwFR9zOplmtMhxVnQxyw=
    </td>
    
    <td>
      DECLARE C CURSOR FOR SELECT * INTO :%col(1) , :%col(2) , :%col(3) , :%col(4) , …
    </td>
    
    <td>
       
    </td>
    
    <td>
      16,892
    </td>
    
    <td>
      597
    </td>
    
    <td>
      yW3catzQzC0KE9euvIJ+o4mDwKc=
    </td>
    
    <td>
      DECLARE C CURSOR FOR SELECT * INTO :%col(1) , :%col(2) , :%col(3) , :%col(4) , :%col(5) , :%col(6) , :%col(7) ,
    </td>
    
    <td>
       
    </td>
    
    <td>
      16,664
    </td>
    
    <td>
      436
    </td>
    
    <td>
      giShyiqNR3K6pZEt7RWAcen55rs=
    </td>
    
    <td>
      DECLARE C CURSOR FOR SELECT * , TKGROUP INTO :%col(1) , :%col(2) , :%col(3) , ..
    </td>
    
    <td>
       
    </td>
    
    <td>
      74,550
    </td>
    
    <td>
      342
    </td>
    
    <td>
      4ZClMPqMfyje4m9Wed0NJzxz9qw=
    </td>
    
    <td>
      DECLARE C CURSOR FOR SELECT …
    </td>
    
    运行日期
    03/16/2019
    03/16/2019
    03/16/2019
    03/16/2019
    03/16/2019

    1:客户站点的实际结果

    INFORMATION_SCHEMA 模式中的表

    除了统计信息外,此模式中的表还会跟踪查询、列、索引等的使用位置。 通常,SQL 语句是起始表,它的连接方式类似于“Statements.Hash=OtherTable.Statement”。

    直接访问这些表以查找一天中开销最大的查询,这一操作的等效查询是...

    SELECT DS.Day,Loc.Location,DS.StatCount,DS.StatTotal,S.Statement,S.HashFROM INFORMATION_SCHEMA.STATEMENT_DAILY_STATS DSleft join INFORMATION_SCHEMA.STATEMENTS  Son S.Hash=DS.Statementleft join INFORMATION_SCHEMA.STATEMENT_LOCATIONS  Locon S.Hash=Loc.Statementwhere Day='08/26/2019'order by DS.stattotal desc

     

    无论你是否考虑建立一个更系统的过程,我都建议每个使用 SQL 处理大型应用程序的人今天都运行这个查询。

    如果某个特定查询显示为高开销,则可以通过运行以下语句获取历史记录

    SELECT DS.Day,Loc.Location,DS.StatCount,DS.StatTotal,S.Statement,S.HashFROM INFORMATION_SCHEMA.STATEMENT_DAILY_STATS DSleft join INFORMATION_SCHEMA.STATEMENTS  Son S.Hash=DS.Statementleft join INFORMATION_SCHEMA.STATEMENT_LOCATIONS  Locon S.Hash=Loc.Statementwhere S.Hash='jDqCKaksff/4up7Ob0UXlkT2xKY='order by DS.Day

     

     
    每日提取统计信息的代码示例
    标准免责声明 - 此示例仅用于说明。 不对其提供支持,也不保证其有效。
    <p>
    </p>
    
    <p>
      <span style="font-size:11pt"><span style="line-height:107%"><span style=""><span style="color:navy">Class DRL.MonitorSQLTask Extends %SYS.Task.Definition</span><br /><span style="color:black">{</span><br /><span style="color:navy">Parameter </span><span style="color:black">TaskName = </span><span style="color:purple">"SQL Statistics Summary"</span><span style="color:black">;</span><br /><span style="color:navy">Method </span><span style="color:black">OnTask() </span><span style="color:navy">As %Status</span><br /><span style="color:black">{</span><br />              <span style="color:blue">set </span><span style="color:olive">tSC</span><span style="color:black">=</span><span style="color:blue">$$$OK</span><br />              <span style="color:blue">TRY </span><span style="color:purple">{</span><br />                             <span style="color:blue">do </span><span style="color:navy">##class</span><span style="color:black">(</span><span style="color:teal">DRL.MonitorSQL</span><span style="color:black">).</span><span style="color:blue">Run</span><span style="color:black">()</span><br />              <span style="color:purple">}</span><br />              <span style="color:blue">CATCH </span><span style="color:olive">exp </span><span style="color:purple">{</span><br />                            <span style="color:blue">set </span><span style="color:olive">tSC</span><span style="color:black">=</span><span style="color:blue">$SYSTEM</span><span style="color:teal">.Status</span><span style="color:black">.</span><span style="color:blue">Error</span><span style="color:black">(</span><span style="color:green">"Error in SQL Monitor Summary Task"</span><span style="color:black">)</span><br />              <span style="color:purple">}</span><br />              <span style="color:blue">quit </span><span style="color:olive">tSC</span><br /><span style="color:black"> }</span><br /><span style="color:black">}</span></span></span></span>
    </p>
    
    <p>
       
    </p>
    
    <p>
      <span style="font-size:11pt"><span style="line-height:107%"><span style=""><span style="color:navy">Class DRL.MonitorSQLText Extends %Persistent</span><br /><span style="color:black">{</span><br /><span style="color:navy">/// Hash of query text</span><br /><span style="color:navy">Property </span><span style="color:black">Hash </span><span style="color:navy">As %String</span><span style="color:black">;</span></span></span></span><br /> 
    </p>
    
    <p>
      <span style="font-size:11pt"><span style="line-height:107%"><span style=""><span style="color:navy">/// query text for hash</span><br /><span style="color:navy">Property </span><span style="color:black">QueryText </span><span style="color:navy">As %String</span><span style="color:black">(</span><span style="color:navy">MAXLEN </span><span style="color:black">= </span><span style="color:navy">9999</span><span style="color:black">);</span><br /><span style="color:navy">Index </span><span style="color:black">IndHash On Hash [ </span><span style="color:navy">IdKey</span><span style="color:black">, </span><span style="color:navy">Unique </span><span style="color:black">];</span><br /><span style="color:black">}</span></span></span></span>
    </p>
    
    <p>
      <span style="font-size:11pt"><span style="line-height:107%"><span style=""><span style="color:navy">/// Summary of very low cost SQL query statistics collected in Cache 2017.1 and later. <br /></span><br /><span style="color:navy">/// Refer to documentation on "SQL Statement Details" for information on the source data. <br /></span><br /><span style="color:navy">/// Data is stored by date and time to support queries over time. <br /></span><br /><span style="color:navy">/// Typically run to summarise the SQL query data from the previous day.</span><br /><span style="color:navy">Class DRL.MonitorSQL Extends %Persistent</span><br /><span style="color:black">{</span><br /><span style="color:navy">/// RunDate and RunTime uniquely identify a run</span><br /><span style="color:navy">Property </span><span style="color:black">RunDate </span><span style="color:navy">As %Date</span><span style="color:black">;</span><br /><span style="color:navy">/// Time the capture was started</span><br /><span style="color:navy">/// RunDate and RunTime uniquely identify a run</span><br /><span style="color:navy">Property </span><span style="color:black">RunTime </span><span style="color:navy">As %Time</span><span style="color:black">;</span><br /><br /><span style="color:navy">/// Count of total hits for the time period for </span><br /><span style="color:navy">Property </span><span style="color:black">TotalHits </span><span style="color:navy">As %Integer</span><span style="color:black">;</span><br /><br /><span style="color:navy">/// Sum of pTime</span><br /><span style="color:navy">Property </span><span style="color:black">SumPTime </span><span style="color:navy">As %Numeric</span><span style="color:black">(</span><span style="color:navy">SCALE </span><span style="color:black">= </span><span style="color:navy">4</span><span style="color:black">);</span><br /><br /><span style="color:navy">/// Routine where SQL is found</span><br /><span style="color:navy">Property </span><span style="color:black">RoutineName </span><span style="color:navy">As %String</span><span style="color:black">(</span><span style="color:navy">MAXLEN </span><span style="color:black">= </span><span style="color:navy">1024</span><span style="color:black">);</span><br /><br /><span style="color:navy">/// Hash of query text</span><br /><span style="color:navy">Property </span><span style="color:black">Hash </span><span style="color:navy">As %String</span><span style="color:black">;</span><br /><br /><span style="color:navy">Property </span><span style="color:black">Variance </span><span style="color:navy">As %Numeric</span><span style="color:black">(</span><span style="color:navy">SCALE </span><span style="color:black">= </span><span style="color:navy">4</span><span style="color:black">);</span><br /><br /><span style="color:navy">/// Namespace where queries are run</span><br /><span style="color:navy">Property </span><span style="color:black">Namespace </span><span style="color:navy">As %String</span><span style="color:black">;</span><br /><br /><span style="color:navy">/// Default run will process the previous days data for a single day.</span><br /><span style="color:navy">/// Other date range combinations can be achieved using the Capture method.</span><br /><span style="color:navy">ClassMethod </span><span style="color:black">Run()</span><br /><span style="color:black">{</span><br />              <span style="color:green">//Each run is identified by the start date / time to keep related items together</span><br />             <br />              <span style="color:blue">set </span><span style="color:olive">h</span><span style="color:black">=</span><span style="color:blue">$h</span><span style="color:black">-1</span><br />              <span style="color:blue">do </span><span style="color:black">..</span><span style="color:blue">Capture</span><span style="color:black">(+</span><span style="color:olive">h</span><span style="color:black">,+</span><span style="color:olive">h</span><span style="color:black">)</span><br /><span style="color:black">}</span><br /><br /><span style="color:navy">/// Captures historic statistics for a range of dates</span><br /><span style="color:navy">ClassMethod </span><span style="color:black">Capture(</span><span style="color:fuchsia">dfrom</span><span style="color:black">, </span><span style="color:fuchsia">dto</span><span style="color:black">)</span><br /><span style="color:black">{</span><br />              <span style="color:blue">set </span><span style="color:olive">oldstatsvalue</span><span style="color:black">=</span><span style="color:blue">$system</span><span style="color:teal">.SQL</span><span style="color:black">.</span><span style="color:blue">SetSQLStatsJob</span><span style="color:black">(-1)</span><br />             <br />                <span style="color:blue">set </span><span style="color:olive">currNS</span><span style="color:black">=</span><span style="color:blue">$znspace</span><br />                <span style="color:blue">set </span><span style="color:olive">tSC</span><span style="color:black">=</span><span style="color:navy">##class</span><span style="color:black">(</span><span style="color:teal">%SYS.Namespace</span><span style="color:black">).</span><span style="color:blue">ListAll</span><span style="color:black">(.</span><span style="color:olive">nsArray</span><span style="color:black">)</span><br />                <span style="color:blue">set </span><span style="color:olive">ns</span><span style="color:black">=</span><span style="color:green">""</span><br />      <span style="color:blue">set </span><span style="color:olive">time</span><span style="color:black">=</span><span style="color:blue">$piece</span><span style="color:black">(</span><span style="color:blue">$h</span><span style="color:black">,</span><span style="color:green">","</span><span style="color:black">,2)</span><br />      <span style="color:blue">kill </span><span style="color:black">^||TMP.MonitorSQL</span><br />                <span style="color:blue">do </span><span style="color:purple">{</span><br />                               <span style="color:blue">set </span><span style="color:olive">ns</span><span style="color:black">=</span><span style="color:blue">$o</span><span style="color:black">(</span><span style="color:olive">nsArray</span><span style="color:black">(</span><span style="color:olive">ns</span><span style="color:black">))</span><br />                               <span style="color:blue">quit</span><span style="color:black">:</span><span style="color:olive">ns</span><span style="color:black">=</span><span style="color:green">""</span><br />                               <span style="color:blue">use </span><span style="color:black">0 </span><span style="color:blue">write </span><span style="color:black">!,</span><span style="color:green">"processing namespace "</span><span style="color:black">,</span><span style="color:olive">ns</span><br />                               <span style="color:blue">zn </span><span style="color:olive">ns</span><br />                                           <span style="color:blue">for </span><span style="color:maroon">dateh</span><span style="color:black">=</span><span style="color:fuchsia">dfrom</span><span style="color:black">:1:</span><span style="color:fuchsia">dto </span><span style="color:purple">{</span><br />                                                          <span style="color:blue">set </span><span style="color:maroon">hash</span><span style="color:black">=</span><span style="color:green">""</span><br />                                                          <span style="color:blue">set </span><span style="color:olive">purgedun</span><span style="color:black">=0</span><br />                                                          <span style="color:blue">do </span><span style="color:purple">{</span><br />                                                                        <span style="color:blue">set </span><span style="color:maroon">hash</span><span style="color:black">=</span><span style="color:blue">$order</span><span style="color:black">(^rINDEXSQL(</span><span style="color:green">"sqlidx"</span><span style="color:black">,1,</span><span style="color:maroon">hash</span><span style="color:black">))</span><br />                                                                        <span style="color:blue">continue</span><span style="color:black">:</span><span style="color:maroon">hash</span><span style="color:black">=</span><span style="color:green">""</span><br />                                                                        <span style="color:blue">set </span><span style="color:olive">stats</span><span style="color:black">=</span><span style="color:blue">$get</span><span style="color:black">(^rINDEXSQL(</span><span style="color:green">"sqlidx"</span><span style="color:black">,1,</span><span style="color:maroon">hash</span><span style="color:black">,</span><span style="color:green">"stat"</span><span style="color:black">,</span><span style="color:maroon">dateh</span><span style="color:black">))</span><br />                                                                        <span style="color:blue">continue</span><span style="color:black">:</span><span style="color:olive">stats</span><span style="color:black">=</span><span style="color:green">""</span><br />                                                                        <span style="color:blue">set </span><span style="color:black">^||TMP.MonitorSQL(</span><span style="color:maroon">dateh</span><span style="color:black">,</span><span style="color:olive">ns</span><span style="color:black">,</span><span style="color:maroon">hash</span><span style="color:black">)=</span><span style="color:olive">stats</span><br />                                                                       <br />                                                          <span style="color:purple">&SQL(</span><span style="color:blue">SELECT  </span><span style="color:green">Location </span><span style="color:navy">into </span><span style="color:maroon">:tLocation </span><span style="color:navy">FROM </span><span style="color:green">INFORMATION_SCHEMA</span><span style="color:black">.</span><span style="color:green">STATEMENT_LOCATIONS </span><span style="color:navy">WHERE </span><span style="color:green">Statement</span><span style="color:black">=</span><span style="color:maroon">:hash</span><span style="color:purple">)</span><br />                                                                        <span style="color:blue">if </span><span style="color:maroon">SQLCODE</span><span style="color:black">'=0 </span><span style="color:blue">set </span><span style="color:olive">Location</span><span style="color:black">=</span><span style="color:green">""</span><br />                                                                        <span style="color:blue">set </span><span style="color:black">^||TMP.MonitorSQL(</span><span style="color:maroon">dateh</span><span style="color:black">,</span><span style="color:olive">ns</span><span style="color:black">,</span><span style="color:maroon">hash</span><span style="color:black">,</span><span style="color:green">"Location"</span><span style="color:black">)=</span><span style="color:maroon">tLocation</span><br />                                                                       <br />                                                          <span style="color:purple">&SQL(</span><span style="color:blue">SELECT  </span><span style="color:green">Statement </span><span style="color:navy">INTO </span><span style="color:maroon">:Statement </span><span style="color:navy">FROM </span><span style="color:green">INFORMATION_SCHEMA</span><span style="color:black">.</span><span style="color:green">STATEMENTS </span><span style="color:navy">WHERE </span><span style="color:green">Hash</span><span style="color:black">=</span><span style="color:maroon">:hash</span><span style="color:purple">)</span><br />                                                                        <span style="color:blue">if </span><span style="color:maroon">SQLCODE</span><span style="color:black">'=0 </span><span style="color:blue">set </span><span style="color:maroon">Statement</span><span style="color:black">=</span><span style="color:green">""</span><br />                                                                        <span style="color:blue">set </span><span style="color:black">^||TMP.MonitorSQL(</span><span style="color:maroon">dateh</span><span style="color:black">,</span><span style="color:olive">ns</span><span style="color:black">,</span><span style="color:maroon">hash</span><span style="color:black">,</span><span style="color:green">"QueryText"</span><span style="color:black">)=</span><span style="color:maroon">Statement</span><br />                                                          <span style="color:purple">} </span><span style="color:blue">while </span><span style="color:maroon">hash</span><span style="color:black">'=</span><span style="color:green">""</span><br />                                                         <br />                                           <span style="color:purple">}</span><br />                <span style="color:purple">} </span><span style="color:blue">while </span><span style="color:olive">ns</span><span style="color:black">'=</span><span style="color:green">""</span><br />                <span style="color:blue">zn </span><span style="color:olive">currNS</span><br />                <span style="color:blue">set </span><span style="color:maroon">dateh</span><span style="color:black">=</span><span style="color:green">""</span><br />                <span style="color:blue">do </span><span style="color:purple">{</span><br />                               <span style="color:blue">set </span><span style="color:maroon">dateh</span><span style="color:black">=</span><span style="color:blue">$o</span><span style="color:black">(^||TMP.MonitorSQL(</span><span style="color:maroon">dateh</span><span style="color:black">))</span><br />                               <span style="color:blue">quit</span><span style="color:black">:</span><span style="color:maroon">dateh</span><span style="color:black">=</span><span style="color:green">""</span><br />                               <span style="color:blue">set </span><span style="color:olive">ns</span><span style="color:black">=</span><span style="color:green">""</span><br />                               <span style="color:blue">do </span><span style="color:purple">{</span><br />                                             <span style="color:blue">set </span><span style="color:olive">ns</span><span style="color:black">=</span><span style="color:blue">$o</span><span style="color:black">(^||TMP.MonitorSQL(</span><span style="color:maroon">dateh</span><span style="color:black">,</span><span style="color:olive">ns</span><span style="color:black">))</span><br />                                             <span style="color:blue">quit</span><span style="color:black">:</span><span style="color:olive">ns</span><span style="color:black">=</span><span style="color:green">""</span><br />                                             <span style="color:blue">set </span><span style="color:maroon">hash</span><span style="color:black">=</span><span style="color:green">""</span><br />                                             <span style="color:blue">do </span><span style="color:purple">{</span><br />                                                         <span style="color:blue">set </span><span style="color:maroon">hash</span><span style="color:black">=</span><span style="color:blue">$o</span><span style="color:black">(^||TMP.MonitorSQL(</span><span style="color:maroon">dateh</span><span style="color:black">,</span><span style="color:olive">ns</span><span style="color:black">,</span><span style="color:maroon">hash</span><span style="color:black">))</span><br />                                                          <span style="color:blue">quit</span><span style="color:black">:</span><span style="color:maroon">hash</span><span style="color:black">=</span><span style="color:green">""</span><br />                                                         <span style="color:blue">set </span><span style="color:olive">stats</span><span style="color:black">=</span><span style="color:blue">$g</span><span style="color:black">(^||TMP.MonitorSQL(</span><span style="color:maroon">dateh</span><span style="color:black">,</span><span style="color:olive">ns</span><span style="color:black">,</span><span style="color:maroon">hash</span><span style="color:black">))</span><br />                                                          <span style="color:blue">continue</span><span style="color:black">:</span><span style="color:olive">stats</span><span style="color:black">=</span><span style="color:green">""</span><br />                                                          <span style="color:green">// The first time through the loop delete all statistics for the day so it is re-runnable</span><br />                                                          <span style="color:green">// But if we run for a day after the raw data has been purged, it will wreck eveything</span><br />                                                          <span style="color:green">// so do it here, where we already know there are results to insert in their place.</span><br />                                                          <span style="color:blue">if </span><span style="color:olive">purgedun</span><span style="color:black">=0 </span><span style="color:purple">{</span><br />                                                                        <span style="color:purple">&SQL(</span><span style="color:blue">DELETE </span><span style="color:navy">FROM </span><span style="color:green">websys</span><span style="color:black">.</span><span style="color:green">MonitorSQL </span><span style="color:navy">WHERE </span><span style="color:green">RunDate</span><span style="color:black">=</span><span style="color:maroon">:dateh </span><span style="color:purple">)</span><br />                                                                        <span style="color:blue">set </span><span style="color:olive">purgedun</span><span style="color:black">=1</span><br />                                                          <span style="color:purple">}</span><br />                                                         <br />                                                          <span style="color:blue">set </span><span style="color:olive">tObj</span><span style="color:black">=</span><span style="color:navy">##class</span><span style="color:black">(</span><span style="color:teal">DRL.MonitorSQL</span><span style="color:black">).</span><span style="color:blue">%New</span><span style="color:black">()</span><br /><br />                                                          <span style="color:blue">set </span><span style="color:olive">tObj</span><span style="color:black">.</span><span style="color:blue">Namespace</span><span style="color:black">=</span><span style="color:olive">ns</span><br />                                                          <span style="color:blue">set </span><span style="color:olive">tObj</span><span style="color:black">.</span><span style="color:blue">RunDate</span><span style="color:black">=</span><span style="color:maroon">dateh</span><br />                                                          <span style="color:blue">set </span><span style="color:olive">tObj</span><span style="color:black">.</span><span style="color:blue">RunTime</span><span style="color:black">=</span><span style="color:olive">time</span><br />                                                          <span style="color:blue">set </span><span style="color:olive">tObj</span><span style="color:black">.</span><span style="color:blue">Hash</span><span style="color:black">=</span><span style="color:maroon">hash</span><br />                                                          <span style="color:blue">set </span><span style="color:olive">tObj</span><span style="color:black">.</span><span style="color:blue">TotalHits</span><span style="color:black">=</span><span style="color:blue">$listget</span><span style="color:black">(</span><span style="color:olive">stats</span><span style="color:black">,1)</span><br />                                                          <span style="color:blue">set </span><span style="color:olive">tObj</span><span style="color:black">.</span><span style="color:blue">SumPTime</span><span style="color:black">=</span><span style="color:blue">$listget</span><span style="color:black">(</span><span style="color:olive">stats</span><span style="color:black">,2)</span><br />                                                          <span style="color:blue">set </span><span style="color:olive">tObj</span><span style="color:black">.</span><span style="color:blue">Variance</span><span style="color:black">=</span><span style="color:blue">$listget</span><span style="color:black">(</span><span style="color:olive">stats</span><span style="color:black">,3)</span><br />                                                          <span style="color:blue">set </span><span style="color:olive">tObj</span><span style="color:black">.</span><span style="color:blue">Variance</span><span style="color:black">=</span><span style="color:blue">$listget</span><span style="color:black">(</span><span style="color:olive">stats</span><span style="color:black">,3)</span><br />                                                         <br />                                                         <span style="color:blue">set </span><span style="color:olive">queryText</span><span style="color:black">=^||TMP.MonitorSQL(</span><span style="color:maroon">dateh</span><span style="color:black">,</span><span style="color:olive">ns</span><span style="color:black">,</span><span style="color:maroon">hash</span><span style="color:black">,</span><span style="color:green">"QueryText"</span><span style="color:black">)</span><br />                                                         <span style="color:blue">set </span><span style="color:olive">tObj</span><span style="color:black">.</span><span style="color:blue">RoutineName</span><span style="color:black">=^||TMP.MonitorSQL(</span><span style="color:maroon">dateh</span><span style="color:black">,</span><span style="color:olive">ns</span><span style="color:black">,</span><span style="color:maroon">hash</span><span style="color:black">,</span><span style="color:green">"Location"</span><span style="color:black">)</span><br />                                                         <br />                                                    <span style="color:purple">&SQL(</span><span style="color:blue">Select </span><span style="color:green">ID </span><span style="color:navy">into </span><span style="color:maroon">:TextID </span><span style="color:navy">from </span><span style="color:green">DRL</span><span style="color:black">.</span><span style="color:green">MonitorSQLText </span><span style="color:navy">where </span><span style="color:green">Hash</span><span style="color:black">=</span><span style="color:maroon">:hash</span><span style="color:purple">)</span><br />                                                          <span style="color:blue">if </span><span style="color:maroon">SQLCODE</span><span style="color:black">'=0 </span><span style="color:purple">{</span><br />                                                                        <span style="color:blue">set </span><span style="color:olive">textref</span><span style="color:black">=</span><span style="color:navy">##class</span><span style="color:black">(</span><span style="color:teal">DRL.MonitorSQLText</span><span style="color:black">).</span><span style="color:blue">%New</span><span style="color:black">()</span><br />                                                                        <span style="color:blue">set </span><span style="color:olive">textref</span><span style="color:black">.</span><span style="color:blue">Hash</span><span style="color:black">=</span><span style="color:olive">tObj</span><span style="color:black">.</span><span style="color:blue">Hash</span><br />                                                                        <span style="color:blue">set </span><span style="color:olive">textref</span><span style="color:black">.</span><span style="color:blue">QueryText</span><span style="color:black">=</span><span style="color:olive">queryText</span><br />                                                                        <span style="color:blue">set </span><span style="color:olive">sc</span><span style="color:black">=</span><span style="color:olive">textref</span><span style="color:black">.</span><span style="color:blue">%Save</span><span style="color:black">()</span><br />                                                          <span style="color:purple">}                                                        </span><br />                                                          <span style="color:blue">set </span><span style="color:olive">tSc</span><span style="color:black">=</span><span style="color:olive">tObj</span><span style="color:black">.</span><span style="color:blue">%Save</span><span style="color:black">()</span><br />                                                         <br />                                                          <span style="color:green">//avoid dupicating the query text in each record because it can be very long. Use a lookup</span><br />                                                          <span style="color:green">//table keyed on the hash. If it doesn't exist add it.</span><br />                                                          <span style="color:blue">if $$$ISERR</span><span style="color:black">(</span><span style="color:olive">tSc</span><span style="color:black">) </span><span style="color:blue">do $system</span><span style="color:teal">.OBJ</span><span style="color:black">.</span><span style="color:blue">DisplayError</span><span style="color:black">(</span><span style="color:olive">tSc</span><span style="color:black">)</span><br />                                                         <br /><br />                                                          <span style="color:blue">if $$$ISERR</span><span style="color:black">(</span><span style="color:olive">tSc</span><span style="color:black">) </span><span style="color:blue">do $system</span><span style="color:teal">.OBJ</span><span style="color:black">.</span><span style="color:blue">DisplayError</span><span style="color:black">(</span><span style="color:olive">tSc</span><span style="color:black">)</span><br />                                             <span style="color:purple">} </span><span style="color:blue">while </span><span style="color:maroon">hash</span><span style="color:black">'=</span><span style="color:green">""</span><br />                               <span style="color:purple">} </span><span style="color:blue">while </span><span style="color:olive">ns</span><span style="color:black">'=</span><span style="color:green">""</span><br />                                                         <br />                <span style="color:purple">} </span><span style="color:blue">while </span><span style="color:maroon">dateh</span><span style="color:black">'=</span><span style="color:green">""</span><br />                <br />                <br />             <br />              <span style="color:blue">do $system</span><span style="color:teal">.SQL</span><span style="color:black">.</span><span style="color:blue">SetSQLStatsJob</span><span style="color:black">(0)</span><br /><span style="color:black">}</span><br /><br /><span style="color:navy">Query </span><span style="color:black">Export(</span><span style="color:fuchsia">RunDateH1 </span><span style="color:navy">As %Date</span><span style="color:black">, </span><span style="color:fuchsia">RunDateH2 </span><span style="color:navy">As %Date</span><span style="color:black">) </span><span style="color:navy">As %SQLQuery</span><br /><span style="color:black">{</span><br /><span style="color:blue">SELECT </span><span style="color:green">S</span><span style="color:black">.</span><span style="color:green">Hash</span><span style="color:black">,</span><span style="color:green">RoutineName</span><span style="color:black">,</span><span style="color:green">RunDate</span><span style="color:black">,</span><span style="color:green">RunTime</span><span style="color:black">,</span><span style="color:green">SumPTime</span><span style="color:black">,</span><span style="color:green">TotalHits</span><span style="color:black">,</span><span style="color:green">Variance</span><span style="color:black">,</span><span style="color:green">RoutineName</span><span style="color:black">,</span><span style="color:green">T</span><span style="color:black">.</span><span style="color:green">QueryText</span><br />              <span style="color:navy">FROM DRL.</span><span style="color:green">MonitorSQL S </span><span style="color:navy">LEFT JOIN DRL.</span><span style="color:green">MonitorSQLText T </span><span style="color:navy">on </span><span style="color:green">S</span><span style="color:black">.</span><span style="color:green">Hash</span><span style="color:black">=</span><span style="color:green">T</span><span style="color:black">.</span><span style="color:green">Hash</span><br />              <span style="color:navy">WHERE </span><span style="color:green">RunDate</span><span style="color:black">>=</span><span style="color:maroon">:RunDateH1 </span><span style="color:black">AND </span><span style="color:green">RunDate</span><span style="color:black"><=</span><span style="color:maroon">:RunDateH2</span><br /><span style="color:black">}</span><br /><span style="color:black">}</span></span></span></span>
    </p>   
    
    <p>
       
    </p>
    
    0
    0 615
    公告 Nicky Zhu · 一月 8, 2021

    现在,InterSystems 系统警报和监视(简称 InterSystems SAM)第 1 版 (v1.0) 发布了预览版本。
      
    InterSystems SAM v1.0 为基于 InterSystems IRIS 的产品提供现代化的监视解决方案。 其可对集群进行高级别查看,并且能够以单节点方式可视化深入探视指标,同时提供警报通知。 该第 1 个版本提供对一百多个 InterSystems IRIS 内核指标的可视化,并且用户可以根据自己的喜好扩展默认提供的 Grafana 模板。

    V1.0 旨在成为简单直观的基准。 请进行尝试并向我们发送反馈,帮助我们使其变得更棒!

    从版本 2019.4 开始,SAM 可以显示来自基于 InterSystems 的实例中的信息

    SAM 仅以容器格式提供。 您将需要 SAM 管理器容器,以及一小组额外的开源_组件_(Prometheus 和 Grafana),它们由组合文件自动添加。

    可从以下位置获取 SAM 组件和 SAM 管理器社区版

    如果您正在旅行,或偏爱通过语音收听有关什么是 SAM 方面的提问与回答,我们为您准备了以下播客:

    <iframe class="castos-iframe-player" frameborder="0" height="160" scrolling="no" src="https://5e18edf067eb59-03854285.castos.com/player/198587" width="100%"></iframe>

    可在此处找到 SAM 文档

    0
    0 181
    文章 Nicky Zhu · 一月 8, 2021 5m read

    在本文中,我们将讨论孤立消息。

    什么是孤立消息

    每个消息正文都与一个保存元数据的消息标头相关联。 标头保存源配置名称、目标配置名称、创建时间、处理时间、关联的消息正文引用、会话信息、消息正文类名、消息状态等信息。 当有消息正文记录没有相应的标头记录时,这些消息正文称为孤立消息正文。 我们将讨论可能导致孤立消息正文产生的原因。

    仅清除标头

    在清除任务设置中,BodiesToo 设置用于指定是否将消息正文与消息标头一起清除。 如果关闭此设置,清除任务将只删除消息标头,而保留消息正文。 这些消息正文将成为孤立记录,因为所引用的标头已被删除。 如果清除消息标头但保留消息正文,则管理门户将无法清除孤立消息正文。 在这种情况下,必须以编程方式清除消息正文。

     

    请参阅有关清除任务的文档

    http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=EGMG_purge#EGMG_purge_basic

    复杂的消息正文类(对象值属性)

    当 Ensemble 清除某条消息正文时,不一定删除该消息正文的对象值属性。 具体来说,只有当其他对象是序列对象或子对象(由关系定义)时,才会删除这些对象。 对于其他对象,必须通过在消息正文类中定义删除触发器或实现 %OnDelete() 方法来适当地处理删除。

    OnDelete 实现的示例代码

    Class Sample.Address Extends %Persistent{
    /// The street address.
    Property Street As %String(MAXLEN = 80);
    /// The city name.
    Property City As %String(MAXLEN = 80);
    /// The 2-letter state abbreviation.
    Property State As %String(MAXLEN = 2);
    /// The 5-digit U.S. Zone Improvement Plan (ZIP) code.
    Property Zip As %String(MAXLEN = 5);
    }
    Class Sample.Person Extends %Persistent{
    /// Person's name.
    Property Name As %String [ Required ];
    /// Person's Social Security number. This is validated using pattern match.
    Property SSN As %String(PATTERN = "3N1""-""2N1""-""4N") [ Required ];
    /// Person's Date of Birth.
    Property DOB As %Date;
    /// Person's home address.
    Property Home As Address;
    /// Person's office address.
    Property Office As Address;
    ///Callback for object deletion
    ClassMethod %OnDelete(oid As %ObjectIdentity) As %Status [ Private ]{
          // Delete the property object references.
          Set tSC = $$$OK, tThis = ##class(Sample.Person).%Open(oid)
          If $ISOBJECT(tThis.Home) Set tSC = ##class(Sample.Address).%DeleteId(tThis.Home.%Id())
          If $ISOBJECT(tThis.Office) Set tSC = ##class(Sample.Address).%DeleteId(tThis.Office.%Id())
          Quit tSC
    }
    ///Callback/Trigger for SQL delete
    Trigger OnDelete [ Event = DELETE ]{
          // Delete the property object references. {%%ID} holds the id of the record being deleted.
          Set tID={%%ID}
          Set tThis = ##class(Sample.Person).%OpenId(tID)
          If $ISOBJECT(tThis.Home) Do ##class(Sample.Address).%DeleteId(tThis.Home.%Id())
          If $ISOBJECT(tThis.Office) Do ##class(Sample.Address).%DeleteId(tThis.Office.%Id())
          Quit
    }
    }
    

    已创建但从未发送到其他主机的消息对象

    当一条消息被发送/转发到其他主机时,Ensemble 会创建一个新的消息标头,并关联相应的消息正文。 如果在业务服务/流程中创建的消息正文/对象实例已保存到磁盘/数据库,但从未在生产过程中发送到其他主机,它将没有关联的标头,并保留为孤立消息正文。 最佳实践是不创建消息正文,除非它将被转发,或者不对该对象实例调用 %Save()(在将消息放入目标配置队列之前,SendRequestSync/SendRequestAsync API 将保存该对象实例)。 这样,除非将消息正文对象发送到其他主机,否则它将不会持久化。

    此问题的最常见原因是开发人员:

    1. 克隆了消息正文,且从未转发克隆的消息正文
    2. 在上下文变量 (BPL) 中创建了消息正文对象,且从未转发。

    孤立消息的影响

    孤立消息不会被清除任务清除。 这些消息将占用磁盘空间,并且随着它们数量的增长,磁盘使用量也会成比例增加。 不仅消息正文数据使用磁盘空间,每条孤立消息正文记录的任何索引/搜索表条目也都占用磁盘空间。

    识别孤立消息

    可以通过查询消息标头和正文来确定孤立消息是否存在。 每个消息标头都引用相应的消息正文。 消息正文对象 Id 引用存储在标头的 MessageBodyId 属性中,消息正文类名存储在标头的 MessageBodyClassName 属性中。

    在 HL7 消息表中查找孤立消息的示例查询:

    SELECT HL7.Id  FROM EnsLib_HL7.Message HL7

       LEFT JOIN Ens.MessageHeader hdr

       ON HL7.Id=hdr.MessageBodyId

       WHERE hdr.MessageBodyId IS NULL

    上面的查询将返回所有没有相应标头的 HL7 消息。 可以将该查询修改为查询任何其他消息类型,只需替换消息正文表名即可。

    清除孤立消息

    管理门户无法提供清除孤立消息正文的方法。 在这种情况下,必须以编程方式清除消息正文。 在 ENSDEMO 数据库中,类 Demo.Util.CleanupSet 提供了一个如何执行此操作的示例。 该例程也执行深度清除,同时处理对象属性引用。

    还可以参考另一个例程来清除孤立消息,但该例程不执行深度清除,只对删除消息正文有帮助。 我在下面附上可下载源码的 github 链接:

    https://gist.github.com/suriyasv/2ed7f2dbcfd8c79f3b9938762c17c0b5

    最佳做法是始终:

    1. 避免编程错误(如前面所讨论)以防止出现孤立消息
    2. 只有需要正文并且知道只能以编程方式清除这些消息正文时,才将清除任务设置成关闭 BodiesToo 设置。
    3. 实施关系或针对持久对象属性实现 OnDelete。

    我希望本文对您构建生产环境有所帮助。 如果您有任何问题或疑虑,请联系我们。 谢谢。

    0
    0 239
    文章 Qiao Peng · 一月 5, 2021 4m read

    各位开发者们大家好!

    此前,我向各位介绍了一个非常好用的运行分析监控面板,它能使消息处理过程中的关键指标可视化,例如入站/出站消息的数量和平均处理时间等。  

    现在,我想用一项许多人已熟悉的工作流程,来展示一个增强型日志监视器——将警告信息作为Production中的消息来处理。我们可以通过创建路由规则来实现对告警消息的过滤和路由,并运用预先构建的组件(例如电子邮件适配器等)来发送粒度级别的通知。  

    如你所知,监视和管理警告信息是确保任何应用程序平稳运行的关键。对诸如HealthShare和IRIS医疗版这样支撑医疗系统运转的一级应用程序和集成引擎来说对告警信息的处理更显得尤为重要。

    让我们先来梳理一下InterSystems产品中已经附带的警告信息监视和管理工具:

    0
    0 263