文章
· 5 hr 前 阅读大约需 7 分钟

孤儿消息数据新手指南--如何清理 200 多 GB 的数据

Purpose of this article

有两篇很棒的有关删除消息关联的孤儿记录的内容以及如何处理孤儿的问题的WRC议最佳实践文章Ensemble Orphaned Messages | InterSystems Developer Community | Best DeleteHelper - A Class to Help with Deleting Referenced Persistent Classes (intersystems.com)
本文并不是要取代 Intersystems 专业人员撰写的这些文章,而是要在此基础上介绍我们如何利用这些信息和其他讨论(包括我们实际清理这些数据的方法)来帮助我们的数据库变得更加紧凑。

情况说明:

我们的备份越来越多。年初的时候,我们遇到过一台服务器被强制故障的情况,需要进行还原。由于数据库庞大,即使复制这个数据库也需要很长时间,更不用说还原重建shadow服务器了。因此,我们不得不决定最终解决这一增长问题。最初的原因已经确定 

  1. 开箱即用的任务或者在某些时候假定已运行,但没有勾选信息体。这是因为在查询其中一个消息体时,我们得到了来自 10 多年前的 ID 1。该任务是最佳实践中提到的默认 Ens.Util.Tasks.Purge。这就引出了流程中的提示 1 

理解你的数据

您在数据库中存储了哪些数据?您是否有必须保存在记录表中的数据?对于您的事务数据,我指的是与message header相关联的报文,您可以使用以下方法进行查询 

SELECT Distinct(MessageBodyClassName) from Ens.MessageHeader

最初,我会查看报文类,打开这些报文类,了解这些报文存储在哪个global中

这样,您就能在一定程度上了解数据的存储位置。

注意,如果您直接保存到数据流(如 %LIBRARY.GLOBALBINARYSTREAM)中,则表明存在孤儿,因为这些孤儿应保存到数据流容器中,我们稍后将介绍这一点


Running Global size report运行 GSize 是一目了然查看数据库大小的工具之一,它可以显示数据在数据库中的存储位置。在终端运行以下步骤 

do ^%GSIZE
Directory name: NAMESAPCE/ =>
All Globals? No => YES
Show details => NO

这可以说明数据在哪里被用掉,从而为您提供指导。

步骤 2 阻止未来的孤儿 

最佳实践指南中对此进行了很好的阐述,但我们还是对其中的步骤进行了细分。

  • 查找直接发送缓存流的类,并将代码迁移到使用流容器的类。
  • 查找未处理的嵌入类--添加手动删除和删除辅助工具 
  • 查看 hl7 消息的几个 %OnSaves 
步骤 3 清理孤儿数据
  1. 每天运行清理任务 
  2. 清理持久化流和其他杂项孤儿数据

 

使用直接流类的班级

Suriya Narayanan 指出,不应直接使用任何 %libary 类。如果不包含数据流,数据流最终会进入 ^Ens.Stream。这些数据不会以良好的引用方式存储,因此您必须在全局中查找您想要保留的最后数据。

在我们的场景中,是 BP 向操作发送消息,而不是将流添加到容器中。

set HTMLDocument=##class(%Library.GlobalBinaryStream).%New()
set tSC=..SendRequestSync(..DocmanRouterName,HTMLDocument, .aResponse,20,"")
//needed sent instead as container
set requestContainer = ##class(Ens.StreamContainer).%New()
set tSc=requestContainer.StreamSet(HTMLDocument)
set tSC=..SendRequestSync(..DocmanRouterName,requestContainer, .aResponse,20,"")
%Saves

并非所有在发送前 %save 的类都会导致问题,只有在没有发送的情况下才会。这种情况可能发生在修改经常保存的 hl7 副本时,也可能发生在临时保存但没有发送的情况下。下面是一个对 hl7 进行操作后没有保存的数据流示例,因此创建了 orpahans。有时,在清除了孤儿和监视器后,我就不会再创建孤儿了,下面的 %save 对象从未被使用过,因此是一个 orpahan,因为只发送了 hl7。在此之前有一个 %new 对象 

 嵌入式对象

下面的删除辅助文档就是一个例子。XML 信息在这方面是出了名的

Class Messages.XML.GenericWif.fileParameters Extends (%Persistent, %XML.Adaptor)
{
Property revisionNumber As %String;
Property primaryLink As Messages.XML.GenericWif.primaryLink;
Property additionalIndexes As Messages.XML.GenericWif.additionalIndexes;

在填充对象后,只有 Messages.XML.GenericWif.fileParameters 会被删除。

1) 进入每个对象,添加类方法和 OnDelete SQL 触发器,检查是否存在子对象并将其删除。可以使用 objectscript 或 sql 进行检查 

ClassMethod %OnDelete(oid As %ObjectIdentity) As %Status [ Private ]
{
      // Delete the property object references.
      Set tSC = $$$OK, tThis = ##class(Messages.XML.GenericWif.fileParameters).%Open(oid)
      If $ISOBJECT(tThis.primaryLink) Set tSC = ##class(Messages.XML.GenericWif.primaryLink).%DeleteId(tThis.primaryLink.%Id())
      If $ISOBJECT(tThis.additionalIndexes) Set tSC = ##class(Messages.XML.GenericWif.additionalIndexes).%DeleteId(tThis.additionalIndexes.%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(Messages.XML.GenericWif.fileParameters).%OpenId(tID)
      If $ISOBJECT(tThis.primaryLink) Do ##class(Messages.XML.GenericWif.primaryLink).%DeleteId(tThis.primaryLink.%Id())
      If $ISOBJECT(tThis.additionalIndexes) Do ##class(Messages.XML.GenericWif.additionalIndexes).%DeleteId(tThis.additionalIndexes.%Id())
      Quit
}

对于 objectscript,您可以打开其中一个 id,我们在测试系统或非生产系统上复制了该 id,例如,键入查询一条最旧的记录,然后在 sql 中查询其他表,例如 Messages_XML_GenericWif.primaryLink。然后,您可以查看是否可以打开其中的子 id。例如,您添加了代码,删除了包含嵌入式消息的 fileParameters 的 id 1。

set a = ##class(Messages.XML.GenericWif.primaryLink).%OpenId(2)
zw a 
//this output the info. Now delete parent
set tSC=##class(Messages.XML.GenericWif.fileParameters).%DeleteId(1)
set a = ##class(Messages.XML.GenericWif.primaryLink).%OpenId(2)
zw a 
//a should be ""

选项 2 是 deleteHelper。它的作用是在代码的 .int 中添加 %onDelete 类方法,而不是 sql。您只需在消息类的扩展中添加 deleteSuper 类,即 

Class Messages.BoltonRenal.Pathology.Outbound.PathologyResult Extends (Ens.Request, SRFT.Utility.DeleteHelper.OnDeleteSuper)
{
Property requestingClinician As %String;
Property department As Messages.BoltonRenal.Pathology.Outbound.Department;
// the on deletesuper is a class like this in the link
include Ensemble
/// A class to help assist in "deep" deleting of an instance, including references to other persistent classes.
/// <br><br>
/// To use simply add as a Super Class in your persistent class<br>
/// The class defines a Generator %OnDelete method that will generate code for your class, 
/// deleting, if needed, references (inclduing collections) to other persistent classes<br>
ClassMethod %OnDelete(oid As %ObjectIdentity) As %Status [ CodeMode = objectgenerator, Private, ServerOnly = 1 ]
{

	// a list ($ListBuild format) of "simple" (non-collection) property names we'll want to delete the references of (because they're Persistent)
	Set delPropNames = ""
                           

如果您转到routine、消息和 .int,就会发现嵌入的对象开始被删除,例如:

 

清理消息

在这一切之后,我们编写了自己的类来清除 150 天之外的信息。我们的想法如下 

  • 在数据查找表中设置要删除的自定义邮件类型(升级版可以将其存储在表格中,使代码更简洁
  • 手动设置删除最旧的 ens email正文。
  • 创建全局数据以存储结果。
  • 手动清理stream和其他残留物

再次提醒,请自行承担删除风险

我们的想法是,你有自己的报文正文类型列表(你的基础可能是 HL7 和报文正文详细信息)。

代码会在其中循环,并从报文头中获取最小 ID(对于报文正文,您需要查看保存到 messagebody 的最旧自定义报文),然后通过以下方式删除 

if msgBodyName="Ens.MessageBody"{
            set tMinMsgId=..MinBodyID
}else
{
    set tMinMsgId=..GetMinimumIDForMessage(rs.MessageBodyClassName)
}

// Min ID is just basically this query   set minIDQuery="SELECT  TOP(1) MessageBodyID  FROM Ens.MessageHeader where MessageBodyClassName=?"
And deletes it. Has added code around it to log 
  SET tSC1=$CLASSMETHOD(className,"%DeleteId",tResult.ID)
 

您可以尝试删除每种类型的数量。在运行 hl7 和报文体时,我们删除了 500000 个,而在运行报文体时,我们删除了 7200000 个。

同样,先在实时数据库的复刻环境(非生产或开发系统)上进行测试 

您不希望删除过多内容导致journal过大,因此需要反复试验。

 

 

如使用后丢失所需数据,我不承担任何责任。

清理流数据

我们有一个小任务,可以用来直接杀死global stream。更新数字并运行 .NET Framework 3.0。要非常清楚你应该保留的最新数据流编号是多少,而且只有当你剩下的数据流是连续的时才有效 

 

ClassMethod StreamPurge() As %Status
{
	set i=1
	while (i<200000){
		k ^CacheStream(i)
		s i=i+1
	}
	q $$$OK
}
讨论 (0)1
登录或注册以继续