Artigo Davi Massaru Teixeira Muta · 2 hr atrás 6m read

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.

Chat U.I

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 data
  • riskyGlobals(minGrowthPct): filtra globals com crescimento percentual acima de um limiar
  • globalHistory(globalName): retorna série histórica de crescimento/uso para um global
  • diskSUMGrowthByLocation(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 🙂