Desenvolvendo uma Camada de Observabilidade com IA para IRIS Globals
Global Guard AI
1 Introdução
Em ambientes que utilizam InterSystems IRIS, os globals são a base física do armazenamento de dados. Apesar de existirem consultas de sistema e ferramentas administrativas para inspeção de métricas, a análise de crescimento de globals costuma ser reativa: o problema geralmente é percebido apenas quando há pressão de disco ou impacto de performance.
O Global Guard AI foi desenvolvido para criar uma camada de observabilidade orientada a snapshots, atendendo à ideia publicadaDPI-I-512 — e tomando como base a sequência de artigos escritos por Ariel Glikman, Sales Engineer at InterSystems:
A partir desses fundamentos, a solução foi expandida para registrar metadados nativos do IRIS de forma periódica, persistir histórico estruturado e calcular crescimento absoluto e percentual, além de permitir a vetorização de padrões de crescimento.


Este artigo descreve como a solução foi construída em três camadas (IRIS, Quarkus e Angular), detalhando a modelagem de dados, a implementação das consultas controladas via tools e a integração entre o backend Java e o IRIS.
2 Visão Geral da Arquitetura
A arquitetura foi organizada em três camadas bem definidas: IRIS, responsável pela coleta e persistência dos snapshots; Quarkus, que implementa o agente conversacional e as tools analíticas; e Angular, que atua como interface de interação com o usuário. Cada camada possui responsabilidades isoladas, evitando acoplamento entre lógica de negócio, acesso a dados e apresentação.

O fluxo de uma requisição é linear: a pergunta parte do frontend Angular, é enviada ao backend Quarkus via REST, o agente identifica a tool apropriada, executa uma consulta SQL explícita no IRIS e retorna os dados estruturados para interpretação pelo modelo. Essa separação garante controle sobre as consultas executadas e mantém o IRIS como única fonte de verdade dos dados analisados.

3 Camada IRIS — Construindo a Base de Observabilidade
A camada IRIS é o núcleo da solução, responsável por coletar, estruturar e armazenar todas as informações que sustentam as análises realizadas pelo sistema. Em vez de inspecionar globals diretamente em nível de nó, a implementação utiliza exclusivamente metadados nativos disponibilizados pelo próprio IRIS, reduzindo impacto operacional e mantendo segurança em ambientes produtivos.
A coleta é baseada principalmente na view %SYS.GlobalQuery_Size, que fornece métricas como espaço alocado e espaço utilizado por global, e nas informações de diretórios obtidas a partir de Config.Databases. Esses dados são capturados periodicamente e persistidos como snapshots históricos, formando uma linha do tempo que permite calcular crescimento absoluto e percentual entre execuções consecutivas.
3.1 Coleta de Metadados — SnapshotGenerator
A coleta diária foi implementada em Embedded Python dentro do IRIS (guard.SnapshotGenerator). O fluxo é: listar os diretórios físicos configurados em Config.Databases, consultar os globals daquele diretório via %SYS.GlobalQuery_Size, calcular o crescimento comparando com o snapshot anterior e persistir o resultado em guard.GlobalSnapshot.
Abaixo estão os trechos centrais do código (simplificados, mas fiéis à implementação):
import iris
# "SELECT DISTINCT %EXACT(Directory) FROM Config.Databases WHERE SectionHeader = 'Databases'"
directories = iris.cls("guard.SnapshotGenerator").listAllDirectories()
for dir in directories:
# "SELECT Name,\"Allocated MB\",\"Used MB\" FROM %SYS.GlobalQuery_Size(?, '', '*','2')"
list_of_globals = iris.cls("guard.SnapshotGenerator").listGlobals(dir)
for global_info in list_of_globals:
insert_stmt = iris.sql.prepare("""
INSERT INTO guard.GlobalSnapshot
(SnapshotDate, GlobalName, Location, AllocatedMB, UsedMB, Tables,
PrevSnapshotId, GrowthMB, GrowthPct)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""")
insert_stmt.execute([... save values...])
Alem disso foram codificadas outras consultas para fornecimento de mais informações, com objetivo de facilitar para o LLM. posteriormente como o getMappedTables:
ClassMethod getMappedTables(globalName As %String) As %List [ Language = python ]
{
import iris
query = """
SELECT Name FROM %Dictionary.CompiledStorage
WHERE DataLocation = ?
UNION ALL
SELECT Name FROM %Dictionary.CompiledStorage
WHERE IdLocation = ?
UNION ALL
SELECT Name FROM %Dictionary.CompiledStorage
WHERE IndexLocation = ?
UNION ALL
SELECT Name FROM %Dictionary.CompiledStorage
WHERE StreamLocation = ?
"""
stmt = iris.sql.prepare(query)
rs = stmt.execute(globalName, globalName, globalName, globalName)
return [row[0] for row in rs]
}
3.3 Vetorização de Padrões de Crescimento — WeeklyVectorGenerator
Além do histórico relacional, o projeto inclui uma etapa opcional de vetorização para comparar padrões de crescimento entre globals. A implementação (guard.WeeklyVectorGenerator) agrega o crescimento (GrowthMB) por dia da semana em uma janela (ex.: últimos 90 dias), normaliza os valores e persiste um vetor de 7 dimensões (Mon..Sun) em guard.GlobalGrowthProfile.
A geração do vetor é feita diretamente no IRIS, agrupando por GlobalName e Location:
ClassMethod run(windowDays As %Integer = 90) [ Language = python ]
{
import iris
from datetime import date, timedelta
end_date = date.today()
start_date = end_date - timedelta(days=windowDays)
query = """
SELECT GlobalName, Location,
AVG(CASE WHEN DAYOFWEEK(SnapshotDate) = 2 THEN GrowthMB ELSE 0 END) AS Mon,
AVG(CASE WHEN DAYOFWEEK(SnapshotDate) = 3 THEN GrowthMB ELSE 0 END) AS Tue,
AVG(CASE WHEN DAYOFWEEK(SnapshotDate) = 4 THEN GrowthMB ELSE 0 END) AS Wed,
AVG(CASE WHEN DAYOFWEEK(SnapshotDate) = 5 THEN GrowthMB ELSE 0 END) AS Thu,
AVG(CASE WHEN DAYOFWEEK(SnapshotDate) = 6 THEN GrowthMB ELSE 0 END) AS Fri,
AVG(CASE WHEN DAYOFWEEK(SnapshotDate) = 7 THEN GrowthMB ELSE 0 END) AS Sat,
AVG(CASE WHEN DAYOFWEEK(SnapshotDate) = 1 THEN GrowthMB ELSE 0 END) AS Sun
FROM guard.GlobalSnapshot
WHERE SnapshotDate BETWEEN ? AND ?
GROUP BY GlobalName, Location
"""
rs = iris.sql.prepare(query).execute(start_date.isoformat(), end_date.isoformat())
insert_stmt = iris.sql.prepare("""
INSERT INTO guard.GlobalGrowthProfile
(GlobalName, Location, WindowDays, FromDate, ToDate,
AVGMon, AVGTue, AVGWed, AVGThu, AVGFri, AVGSat, AVGSun,
WeeklyVector)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, TO_VECTOR(?, DOUBLE))
""")
for row in rs:
globalName, location = row[0], row[1]
values = [float(row[i] or 0) for i in range(2, 9)]
total = sum(values)
if total <= 0:
continue
normalized = [v / total for v in values]
vec_str = "[" + ",".join(f"{v:.6f}" for v in normalized) + "]"
insert_stmt.execute(
globalName, location, windowDays,
start_date.isoformat(), end_date.isoformat(),
*values,
vec_str
)
}
O resultado é um vetor normalizado que representa distribuição de crescimento ao longo da semana, permitindo consultas de similaridade no IRIS (ex.: via VECTOR_COSINE) para encontrar globals com comportamento semelhante mesmo quando os tamanhos absolutos são diferentes.
4 Camada Quarkus — Agente Conversacional e Tools Controladas
A camada Quarkus implementa o backend que expõe a API REST, orquestra o agente conversacional e executa as análises consultando o IRIS. O ponto central aqui é que o modelo de linguagem não acessa o banco diretamente: ele interage apenas por meio de tools Java previamente definidas, cada uma associada a consultas SQL explícitas.
Na prática, o backend recebe a pergunta, o agente (LangChain4j) seleciona a tool apropriada e a execução ocorre via EntityManager com NativeQuery. O resultado retorna estruturado para o agente, que apenas interpreta os dados e produz a resposta em texto para o frontend.

4.1 Estrutura do Backend
O backend foi organizado separando claramente responsabilidades entre Resource, Service e Tools. A classe REST expõe o endpoint /ai/globals/ask/{chatId}, recebendo a pergunta e delegando a execução ao serviço principal do agente. O GlobalAiService registra as tools disponíveis e integra o LangChain4j com a memória associada ao chatId.
As tools foram divididas em dois grupos principais: GlobalRepositoryTools, responsável por consultas sobre snapshots históricos, e GlobalQueryTools, responsável por consultas diretas ao IRIS e por operações envolvendo vetores. Cada método anotado com @Tool possui descrição explícita, orientando o modelo sobre quando e como utilizá-lo.
package iris.global.ia;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.UserMessage;
import io.quarkiverse.langchain4j.RegisterAiService;
import iris.global.ia.tools.DateTools;
import iris.global.ia.tools.GlobalQueryTools;
import iris.global.ia.tools.GlobalRepositoryTools;
@RegisterAiService(
chatMemoryProviderSupplier = ChatMemoryProviderFactory.class,
tools = { GlobalRepositoryTools.class,
GlobalQueryTools.class , DateTools.class})
public interface GlobalAiService {
@SystemMessage("""
You are **Global Guard AI**, a database observability assistant specialized in **InterSystems IRIS globals**.
CORE PRINCIPLE:
- Use chat history for conversational context and user-provided facts
- Never rely on assumptions or inferred metrics.
- All operational and analytical answers MUST be based strictly on data
returned by tools.
MISSION:
Help DBAs and operators **understand, monitor, and control IRIS global growth and disk usage** using **snapshot-based metrics**.
CAPABILITIES:
- Analyze daily and point-in-time global growth.
- Identify fast-growing or high-risk globals.
- Explain historical growth trends.
- Aggregate and interpret disk usage by database location.
- Translate raw metrics into actionable operational insights.
DATA RULES (CRITICAL):
- If a user asks a question that requires a snapshot date and does not explicitly provide one, you MUST call the DateTools.today tool to obtain the current date.
- Do NOT ask the user for the date in this case.
- Treat the returned date as explicitly known and valid for subsequent tool calls.
- **Use only tool-provided data.**
- **Never invent, estimate, extrapolate, or assume** metrics, dates, thresholds, global names, or locations.
- Always treat any date explicitly provided by the user as valid for querying snapshots, even if it appears to be in the future relative to model knowledge.
- Do NOT compare user-provided dates with the model's internal calendar.
TOOL USAGE RULES:
- Call a tool ONLY when all required parameters are explicitly known.
- EXCEPTION: snapshot date resolution follows the DATE RESOLUTION rules above.
- NEVER guess or infer parameters.
- If parameters (other than snapshot date) are missing or ambiguous,
ask the user for clarification BEFORE calling any tool.
- Avoid unnecessary or speculative tool calls.
- Global names may include dots (`.`) and all parts of the name must be used exactly as provided, for example:
- `^GLOBAL` is different from `^GLOBAL.SUB` and both are different from `^GLOBAL.SUBPART` they are all distinct globals and should be treated as such.
- another example: `guard.GlobalSnapshotD`
- Do not truncate or split global names when calling tools.
- IMPORTANT: Each tool must be used strictly according to its documented purpose.
- If the user asks about researching global growth, use the riskyGlobals tool.
ANALYSIS RULES:
- Treat snapshot data as **point-in-time measurements**.
- Growth metrics:
- **Absolute growth (MB)** vs **Relative growth (%)** — do not confuse.
- Historical analysis requires **ordered timelines**, not single snapshots.
COMMUNICATION STYLE:
- Concise, clear, and **DBA-friendly**.
- Prefer **actionable conclusions** over raw data dumps.
- Highlight **risks, anomalies, and unusual patterns**.
- Suggest preventive actions when relevant:
- Increased monitoring
- Cleanup or archiving
- Capacity planning review
GOAL:
Enable **safe, data-driven decisions** regarding global storage, growth behavior, and operational risk in InterSystems IRIS.
""")
String answer(@MemoryId String chatId, @UserMessage String question);
}
4.1.1 GlobalRepositoryTools
GlobalRepositoryTools concentra as tools que consultam dados históricos persistidos na tabela/classe guard.GlobalSnapshot. Essas tools são usadas quando a pergunta envolve datas, comparações entre snapshots, ranking de crescimento ou agregações por location.
Na implementação, cada método é exposto como @Tool e executa SQL explícito via EntityManager.createNativeQuery(...), retornando resultados estruturados (DTO/Map) para o agente interpretar. Exemplos de operações típicas nesta classe:
topGrowingGlobals(date): lista os globals com maior crescimento em uma datariskyGlobals(minGrowthPct): filtra globals com crescimento percentual acima de um limiarglobalHistory(globalName): retorna série histórica de crescimento/uso para um globaldiskSUMGrowthByLocation(snapshotDate): agrega crescimento por location em uma data
Exemplo (padrão de implementação com SQL nativo + retorno estruturado):
Nota: Tools possuem uma engenharia de prompt complexa, aqui está apena um resumo para ser maid didático.
@Tool("List top globals by absolute growth (MB) for a given snapshot date.")
public List<Map<String, Object>> topGrowingGlobals(String date) {
String sql = """
SELECT GlobalName, Location, UsedMB, GrowthMB, GrowthPct
FROM guard.GlobalSnapshot
WHERE SnapshotDate = :snapshotDate
ORDER BY GrowthMB DESC
""";
@SuppressWarnings("unchecked")
List<Object[]> rows = em.createNativeQuery(sql)
.setParameter("snapshotDate", date)
.setMaxResults(20)
.getResultList();
List<Map<String, Object>> result = new ArrayList<>();
for (Object[] r : rows) {
result.add(Map.of(
"globalName", r[0],
"location", r[1],
"usedMB", r[2],
"growthMB", r[3],
"growthPct", r[4]
));
}
return result;
}
Esse padrão se repete nas demais tools: a query é conhecida e controlada pelo backend, e o agente apenas escolhe qual tool chamar com base na pergunta do usuário.
4.1.2 GlobalQueryTools
Enquanto GlobalRepositoryTools atua sobre dados históricos já persistidos, GlobalQueryTools é focada em consultas atuais e em tempo real, acessando diretamente as views nativas do IRIS ou os perfis vetorizados gerados previamente. Essa classe é utilizada quando a pergunta envolve o estado atual do sistema ou comparações baseadas em similaridade.
As tools desta classe executam consultas diretamente sobre %SYS.GlobalQuery_Size, %SYS.GlobalQuery_NameSpaceList ou sobre guard.GlobalGrowthProfile (no caso de buscas vetoriais). Assim como na classe anterior, todas as consultas são explícitas e executadas via EntityManager.createNativeQuery(...), mantendo controle total sobre os comandos SQL executados.
Exemplos de responsabilidades típicas dessa classe incluem:
- Consulta de uso de disco atual por location
- Listagem de globals em um namespace específico
- Busca de globals com comportamento semelhante usando
VECTOR_COSINE - Consultas agregadas sem depender exclusivamente do snapshot histórico
Essa separação entre histórico (GlobalRepositoryTools) e tempo real (GlobalQueryTools) permite organizar melhor as responsabilidades e deixa claro quando a análise depende de snapshots persistidos ou de dados atuais do sistema.
Abaixo está um exemplo simplificado de uma tool que consulta diretamente a view %SYS.GlobalQuery_Size para obter métricas atuais de espaço utilizado e alocado por global em um diretório específico:
@Tool("Show current disk usage for globals in a given database directory.")
public List<Map<String, Object>> currentDiskUsageByDirectory(String directory) {
String sql = """
SELECT Name, "Allocated MB", "Used MB"
FROM %SYS.GlobalQuery_Size(:directory, '', '*', '2')
ORDER BY "Used MB" DESC
""";
@SuppressWarnings("unchecked")
List<Object[]> rows = em.createNativeQuery(sql)
.setParameter("directory", directory)
.getResultList();
List<Map<String, Object>> result = new ArrayList<>();
for (Object[] r : rows) {
result.add(Map.of(
"globalName", r[0],
"allocatedMB", r[1],
"usedMB", r[2]
));
}
return result;
}
Nesse caso, a consulta é executada diretamente no IRIS, sem depender da tabela de snapshots. Isso permite responder perguntas como “Show me disk usage today” com base no estado atual do sistema, mantendo a mesma estratégia de execução controlada por SQL explícito.
5 Camada Angular — Interface Conversacional
O frontend em Angular foi implementado como uma interface de chat simples, com responsabilidade restrita a capturar a pergunta do usuário, enviar a requisição ao backend e renderizar a resposta. Não existe lógica analítica no navegador: toda decisão sobre ferramentas, consultas e interpretação de dados ocorre no Quarkus.
A integração é feita via REST chamando o endpoint POST /ai/globals/ask/{chatId}, enviando o texto no corpo da requisição e exibindo a resposta retornada. O chatId é mantido no frontend para garantir continuidade de conversa (memória) no backend.
Conclusão
O Global Guard AI demonstra como é possível combinar recursos nativos do InterSystems IRIS — como %SYS.GlobalQuery_*, modelagem %Persistent e %Vector — com um backend moderno em Quarkus e um agente baseado em tools para criar uma camada de observabilidade controlada e orientada a dados reais.
A arquitetura em três camadas garante separação clara de responsabilidades, controle sobre as consultas executadas e previsibilidade operacional. O IRIS atua como fonte única de verdade, o Quarkus orquestra as tools com SQL explícito e o modelo de linguagem limita-se à interpretação dos resultados, preservando segurança e auditabilidade.
Nota:
O projeto está concorrendo no InterSystems Open Exchange Contest 2026:
https://openexchange.intersystems.com/contest/45#569🗳 Voting Ends: 01 Mar, 2026, 11:59:59 PM EST
Se você gostou do projeto e acredita que ele contribui para o ecossistema InterSystems, considere apoiar com seu voto 🙂