Nova postagem

検索

Artigo
· Jun. 30, 2024 7min de leitura

孤儿消息数据新手指南--如何清理 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
}
Discussão (0)1
Entre ou crie uma conta para continuar
Artigo
· Jun. 30, 2024 7min de leitura

Exemplo de aplicação Flask com SQLAlchemy-IRIS - Parte 2

Por que conectar Flask com InterSystems IRIS?

A primeira coisa que vem à mente quando pensamos sobre a combinação de Flask com IRIS é um portal para interagir com seus clientes e parceiros. Um bom exemplo seria um website para pacientes acessarem seus exames clínicos. É claro, nesse caso seria necessário uma boa camada de segurança, que nós não cobrimos no último artigo. No enteando, podemos adicioná-la sem esforços com o Werkzeug, por exemplo.

Websites que fazem uma ponte entre a relação entre seus produtos e clientes são, de fato, um exemplo excelente do que você pode conseguir com essa conexão de tecnologias, já que o Flask é flexível, você pode facilmente adicionar e mudar quaisquer recursos com ele ou até fazer mais se tiver experiência com CI/CD.

Vamos dar uma olhada em alguns truques que você pode fazer. Se você se lembrar d primeira aplicação mostrada logo no início, deve se lembrar que o Flask não necessita de uma estrutura complexa para funcionar, o que significa que você pode construí-lo do zero com apenas alguns minutos de programação. Isso significa que você pode rapidamente criar múltiplas interfaces amigáveis ao usuário e portais interativos para exibir e analisar dados. Por exemplo, você pode rastrear cada parte de uma produção separadamente. Além disso, você pode providenciar quaisquer dos recursos InterSystems IRIS que usa frequentemente numa plataforma no-code bonita.

Eu trabalhei para a Innovatium desde o ano passado (2022), e essa experiência me ajudou a encontrar muitas situações onde seria muito prático observar a memória usada em resultados de consultas, requisições e tabelas de tempo em tempo. Esse problema é fácil de contornar, fazendo um portal similar ao criado no meu artigo anterior sobre Django. Já que desenvolver com um Flask é muito mais rápido do que com Django, seria menos custoso para construir algumas plataformas diferentes para cada projeto e para cada tabela que queremos rastrear. Além disso, se tiver um tempo extra, pode automatizar sua plataforma para receber atualizações ao vivo com Flask-SocktetIO.

Com um template em mãos para iniciar seus projetos, o desenvolvimento pode acelerar ainda mais. É por isso que na próxima seção vou introduzir um template Flask-IRIS CRUD publicado no OpenExchange (o que construímos na parte 1 dessa série de artigos). O que eu quero dizer é que se você não tem tempo para investigar todos os detalhes percorridos no último artigo, você pode apenas fazer o download da aplicação e continuar trabalhando com o seu conhecimento atual sobre seu projeto sem preocupações.
 

A aplicação OpenExchange

Eu decidi escrever essa série de artigos inspirada pela ideia mencionada em Example of Flask application with SQLAlchemy IRIS. No entanto, já que talvez você já esteja familiarizado com frameworks web em Python e a lógica CRUD, vou introduzir você a uma aplicação para colocar as mãos na massa imediatamente. Dessa maneira, você não precisa perder tempo lendo antes de o seu projeto começar a tomar forma.

Siga os passos abaixo para fazer o download e começar a usar a aplicação, e veja como é fácil. Tem uma versão mais sucinta do tutorial de utilização que segue no arquivo README no repositório GitHub. Você também pode verificar todo link relacionado ao app em sua página Open Exchange.

 

Guia de instalação

Antes de desenvolver projetos com Python, é sempre uma boa prática configurar um ambiente virtual. Isso irá te ajudar a adicionar todos os requisitos, configurações e variáveis estritamente a esse projeto, sem afetar seu computador ou outros projetos.

No Windows/Linux/macOS, abra o terminal no diretório que quer começar seu projeto e crie um ambiente virtual lá com o comando a seguir:
python -m venv .venv-folder

O comando mencionado acima pode ser desmembrado da seguinte maneira: "python" vai definir um ambiente Python para os comandos que o seguem. A flag "-m" vai rodar um módulo (nesse caso, venv), e ".venv-folder" vai criar uma pasta chamada venv-folder no diretório atual (referenciado pelo ponto), onde o módulo venv irá rodar. Obs.: venv = virtual enviroment = ambiente virtual.

Seu próximo passo será ativar o ambiente criado antes de fazer o download dos requisitos e propriamente iniciar seu projeto.

Se você usa macOS ou Linux, digite "..venv/bin/activate". No Windows, o comando similar ".venv\Scripts\activate" deve rodar o arquivo apropriado para a tarefa.

Em seguida, você pode clonar o repositório com git clone.
git clone https://github.com/heloisatambara/flask-iris.git

Finalmente, instale os requisitos com o Python Install Package (PIP) e você estará pronto para começar a programar.
pip install -r requirements.txt

Como você pode comprovar no arquivo referenciado, o comando acima irá instalar pelo menos a versão 2.3.3 do Flask, pelo menos a versão 3.1.1 do Flask-SQLAlchemy e pelo menos a versão 0.10.5 do sqlalchemy-iris, desenvolvida pela ^CaretDev.

 

Guia de utilização

Primeiro você deve conectar a aplicação à base de dados desejada e o namespace. Como você já deve ter aprendido no artigo anterior, essa conexão é feita no arquivo flaskr-iris/database.py, com o formato "iris://usuário:senha@host:porta/NAMESPACE", conforme especificado na documentação SQLALchemy para a função create_engine(). Verifique o exemplo abaixo.
    engine = create_engine("iris://_SYSTEM:sys@localhost:1972/SAMPLE")
  Agora você pode rodar a aplicação web e ver como o exemplo que você desenvolveu funciona, depois de checar se a instância está rodando, com o comando a seguir.
...\flask-iris> flask --app flaskr-iris run --debug

Para adaptar a aplicação às susas necessidades, comece editando o arquivo flaskr-iris/models.py para refletir os dados que você vai trazer do IRIS para o Python. O exemplo em clone do GitHub já cobre um grande número de casos, como você pode verificar no artigo anterior na seção dedicada a modelos. Se precisar de algo a mais, pode revisar a documentação relacionada em Flask-SQLAlchemy ou SQLAlchemy, sobre definir modelos e tabelas declarativas, respectivamente.

Então, é sugerido que você continue desenvolvendo editando os arquivos em flaskr-iris/templates/, para adaptar a exibição aos seus dados. A documentação Jinja contém todas as opções para programar seus templates.

Em seguida, você pode usar os exemplos em auth.py e blog.py para criar suas views. Não esqueça de registrar toda nova Blueprint na fábrica create_app(), no arquivo __init__.py.

Finalmente, os arquivos dentro da pasta estática devem te dar um avanço para adicionar o estilo da sua marca na página web.
 

Seção Bônus - Erros que encontrei e como corrigí-los

  • Começando com o que pode parecer um erro de principiante, mas pode acontecer com qualquer um: se você esqueceu de inicializar a instância IRIS que está conectando, pode receber a mensagem a seguir:
sqlalchemy.exc.OperationalError: (intersystems_iris.dbapi._DBAPI.OperationalError) [WinError 10061] No connection could be made because the target machine actively refused it.

É claro, tudo o que você precisa fazer aqui é apertar "Start InterSystems IRIS" e rodar seu app Flask novamente.
 

  • O próximo erro pode parecer um pouco mais desafiador:
RuntimeError: Working outside of request context.

This typically means that you attempted to use functionality that
needed an active HTTP request. Consult the documentation on testing
for information about how to avoid this problem.
db.init_app(app)

Esse erro pode ocorrer por causa de algumas diferentes razões. Se você o encontrou testando, use o test_client para simular uma requisição completa. Caso contrário, você pode precisar mover seu código a uma view. Entretanto, eu encontrei esse erro numa situação atípica: eu não tinha iniciado a base de dados com o app Flask. Para consertar isso, quando você definir sua base de dados com db = SQLAlchemy() ou algo similar, você pode conectar ao app diretamente com o seguinte:

app = Flask(__name__)
db = SQLAlchemy(app)

Outra alternativa é, se você está trabalhando com uma fábrica de aplicação, como mostrado no artigo anterior, você pode conectar ao app posteriormente, chamando init_app():

db = SQLAlchemy()

def create_app():
	app = Flask(__name__)
	db.init_app(app)
  • Se você escolher a abordagem da parte 1 desse artigo, você irá notar que toda vez que roda sua aplicação, ela tentará criar todas as bases de dados dos modelos que você definir. Isso significa que você deve tratar esse tipo de erro com um bloco "try/except". Uma opção é ignorar o erro se uma tabela já existe, mas não esqueça de corrigir outros erros também. Contudo, se você escolher essa solução, você pode ter que deletar todas as tabelas manualmente toda vez que fizer mudanças nelas. Outra opção é usar o atributo "checkfirst" quando chamar a função create_all(). Isso deve ignorar as tabelas que já existem, então as mudanças não serão aplicadas. Se isso satisfaz as suas necessidades, aproveite. Ainda assim, para uma implementação de maior escala, você pode usar o SQLAlchemy para derrubar tabelas criadas anteriormente, antes de rodar o create_all(). Cuidado: as tabelas serão criadas automaticamente com o formato de nome DefaultSchema_snake_case_model_name. Para ilustrar, no exemplo que exploramos, as tabelas foram construídas no IRSI com os nomes SQLUser_user e SQLUser_post.
try:
    with app.app_context():
        db.create_all()
except DatabaseError as err:
    if 'already exists' in err._sql_message():
        print("Databases already exist.")
    else:
        print(err) 
  • Por último, mas não menos importante, quando você coloca modelos num arquivo difetente do que você usou para rodar a função create_all(), você pode ter a impressão de que não funciona. Isso acontece porque você deve importar todo modelo que quiser montar antes de criá-los.
from .models import User, Post
db.init_app(app)
try:
    with app.app_context():
        db.create_all()
Discussão (0)1
Entre ou crie uma conta para continuar
Discussão (7)5
Entre ou crie uma conta para continuar
Discussão (6)2
Entre ou crie uma conta para continuar
Discussão (1)1
Entre ou crie uma conta para continuar