Escrito por

Software Engineer at Zarmik
Artigo Heloisa Paiva · Jun. 3 6m read

Particionamento manual

O IRIS 2026.1 trouxe as Partitioned Tables (tabelas particionadas) como uma nova opção para grandes conjuntos de dados. É uma excelente melhoria, pois padroniza esse recurso de forma nativa.

No entanto:
Já era possível atingir esse mesmo objetivo antes, atendendo aos requisitos e deixando espaço para a criatividade. Uma abordagem menos elegante, com um pouco mais de código e menos automatismos.

    

A necessidade é evidente.

Um exemplo:
Imagine que você tem uma tabela PERSON contendo Clientes, Funcionários, Parceiros, Fornecedores... A abordagem clássica seria indexar os diferentes papéis (roles) ou herdar classes individuais a partir de uma classe comum.
O impacto imediato:
Todos os registros vão parar nas mesmas 3 Globals de Dados (Data), Índices (Index) e Streams.
Isso significa que buscar por 1 entre 37 Funcionários pode levar, em média, o mesmo tempo que buscar por 1 entre 277.876 Clientes ou 1.612 Fornecedores.

Simplificando: eles compartilham do mesmo ecossistema de armazenamento. 

Dividir o armazenamento deles em Globals separadas promete uma boa melhoria de performance.

A abordagem mais simples seria apenas usar 4 tabelas com nomes diferentes, gerando todo aquele impacto de nomenclatura nos seus utilitários de manutenção. Muitas implementações em produção funcionam assim hoje. A @Iryna Mykhailova descreve isso em seu artigo Storing data for classes and their superclass separately.
     

Existe uma abordagem mais sofisticada

Há muito tempo, estes 2 Class Parameters permitem uma solução bastante dinâmica:

  • O parâmetro DEFAULTGLOBAL é usado como a raiz global padrão para as palavras-chave de armazenamento DATALOCATION, IDLOCATION, INDEXLOCATION e STREAMLOCATION.
    • Por exemplo, se DEFAULTGLOBAL = "^Demo.Person" isso compila como:                                   
      • DATALOCATION = ^Demo.PersonD
      • IDLOCATION = ^Demo.PersonD
      • INDEXLOCATION = ^Demo.PersonI
      • STREAMLOCATION = ^Demo.PersonS                    
  • O parâmetro MANAGEDEXTENT pode ser configurado como 0 (zero) para fazer com que o Extent Manager ignore esta classe. Se configurado como 1, o Extent Manager registrará as globals usadas pela classe e detectará colisões.    

Agora podemos usar o DEFAULTGLOBAL em combinação com indirection (indireção):

  • Parâmetro DEFAULTGLOBAL como STRING = "@%rcc";         
    • Atribua as Globals por indireção Em Tempo de Execução compilado como:                                   
      • <DataLocation>@%rccD</DataLocation>
      • <DefaultData>RCCDefaultData</DefaultData>                    
      • <IdLocation>@%rccD</IdLocation>                    
      • <IndexLocation>@%rccI</IndexLocation>                    
      • <StreamLocation>@%rccS</StreamLocation>
  • Parâmetro MANAGEDEXTENT como INTEGER = 0;                   
    • Permite uma alteração dinâmica das suas Globals de armazenamento e protege contra erros durante a compilação da classe.   

    Você só precisa fornecer os nomes corretos para estas 3 variáveis antes do uso:

  •         set %rccD ="^"_%rcc_"D"    
  •         set %rccI ="^"_%rcc_"I"      
  •         set %rccS ="^"_%rcc_"S"    

    Agrupar isso em um pequeno ClassMethod é bem tranquilo, e projetá-lo como uma SqlProcedure o deixa disponível também para qualquer acesso SQL.

    Exemplo para a Global ^Person*:

SAMPLES>write ##class(RCC).%rcc("Person")
1
SAMPLES>zwrite
 
%rccD="^PersonD"
%rccI="^PersonI"
%rccS="^PersonS"
%sqlcontext=<OBJECT REFERENCE>[2@%Library.ProcedureContext]
SAMPLES>write ##class(RCC).Populate(11)
11

SAMPLES>:sql
SQL Command Line Shell
----------------------------------------------------
The command prefix is currently set to: <<nothing>>.
Enter <command>, 'q' to quit, '?' for help.
[SQL]SAMPLES>>select * from RCC where %rcc('Person')=1
1.      select * from RCC where %rcc('Person')=1
 
| ID | city | dob | name | score |
| -- | -- | -- | -- | -- |
| 1 | Vail | 40558 | Leiberman,Sally E. | 97 |
| 2 | Chicago | 58285 | Ott,Lydia H. | 44 |
| 3 | Pueblo | 58873 | Hertz,Usha L. | 45 |
| 4 | Gansevoort | 40744 | Gomez,Heloisa O. | 53 |
| 5 | Ukiah | 34822 | Macrakis,Keith S. | 39 |
| 6 | Ukiah | 31655 | Gore,Chris N. | 9 |
| 7 | Hialeah | 39868 | Grabscheid,Amanda O. | 66 |
| 8 | Vail | 60410 | Burroughs,Greta W. | 88 |
| 9 | Tampa | 43499 | Zevon,Al W. | 15 |
| 10 | Tampa | 59787 | Feynman,Juanita Q. | 93 |
| 11 | Miami | 53081 | Zucherro,Bart R. | 42 |
 
11 Rows(s) Affected
statement prepare time(s)/globals/cmds/disk: 0.0774s/44,458/226,219/0ms
          execute time(s)/globals/cmds/disk: 0.0004s/12/1,938/0ms
                                query class: %sqlcq.SAMPLES.cls1
---------------------------------------------------------------------------
[SQL]SAMPLES>>	-

     Mesma sessão com a Global ^mtemp.Work*:

SAMPLES>write ##class(RCC).%rcc("mtemp.Work")
1
SAMPLES>zw
 
%rccD="^mtemp.WorkD"
%rccI="^mtemp.WorkI"
%rccS="^mtemp.WorkS"
%sqlcontext=<OBJECT REFERENCE>[2@%Library.ProcedureContext]
SAMPLES>w ##class(RCC).Populate(5)
5

SAMPLES>:sql
SQL Command Line Shell
----------------------------------------------------
 
The command prefix is currently set to: <<nothing>>.
Enter <command>, 'q' to quit, '?' for help.
[SQL]SAMPLES>>select name,city from RCC where %rcc('mtemp.Work')=1 order by city 
2.      select name,city from RCC where %rcc('mtemp.Work')=1 order by city
 
| name | city |
| -- | -- |
| Gaboriault,Quentin O. | Bensonhurst |
| Baker,Andrew Q. | Fargo |
| Zweifelhofer,Clint T. | Miami |
| Fives,Patrick T. | Newton |
| Alton,Geoffrey I. | Pueblo |
 
5 Rows(s) Affected
statement prepare time(s)/globals/cmds/disk: 0.0596s/38,492/210,709/0ms
          execute time(s)/globals/cmds/disk: 0.0002s/6/919/0ms
                                query class: %sqlcq.SAMPLES.cls5
---------------------------------------------------------------------------
[SQL]SAMPLES>>

    E a definição da classe (Class Definition):

        Class User.RCC Extends (%Persistent, %Populate) [ Final ]
        {
        Parameter MANAGEDEXTENT As INTEGER = 0;
        Parameter DEFAULTGLOBAL As STRING = "@%rcc";
        
        Property name As %String(POPSPEC = "Name()", TRUNCATE = 1);
        Property city As %String(POPSPEC = "City()", TRUNCATE = 1);
        Property dob As %Date;
        Property score As %Integer(POPSPEC = "Integer(0,100)");
        //
        Index nameIDX On name [ Data = city ];
        Index cityIDX On city As SQLSTRING [ Type = index ];
        Index nsIDX On (name, score);
        Index ncIDX On (name, city);
        Index dobIDX On dob [ Data = name ];
        //
        ClassMethod %rcc(%rcc = "") As %Integer [ SqlName = %rcc, SqlProc ]
        {
        if '$l(%rcc) set %rcc="mtemp."_$job
        set %rccD="^"_%rcc_"D"  
        set %rccI="^"_%rcc_"I"  
        set %rccS="^"_%rcc_"S"
        quit $$$OK
        }
        //
        Storage Default
        {
        <Data name="RCCDefaultData">
        <Value name="1">
        <Value>name</Value>
        </Value>
        <Value name="2">
        <Value>city</Value>
        </Value>
        <Value name="3">
        <Value>dob</Value>
        </Value>
        <Value name="4">
        <Value>score</Value>
        </Value>
        </Data>
        <DataLocation>@%rccD</DataLocation>
        <DefaultData>RCCDefaultData</DefaultData>
        <IdLocation>@%rccD</IdLocation>
    <IndexLocation>@%rccI</IndexLocation>
        <StreamLocation>@%rccS</StreamLocation>
        <Type>%Storage.Persistent</Type>
        }
        }    

Resumo:

Tudo o que você precisa para usar essa abordagem via ObjectScript:
 defina suas Globals de acesso antes do uso
do ##class(RCC).%rcc("nome_da_sua_global")

Para uso via SQL, utilize a respectiva condição static WHERE:

SELECT ........   from RCC WHERE %rcc('nome_da_sua_global')=1 ....

E isso funciona para qualquer referência válida de Global, inclusive para Process Private Globals, Globals temporárias, referências estendidas (Extended references), etc...
Caso nada seja fornecido, defina por padrão "mtemp."_$job

Comments

Aziz Cotrim · Jun. 3

Excelente artigo, Heloisa! Estou começando agora no IRIS vindo de C#/.NET e o exemplo da tabela PERSON deixou bem claro pra mim por que registros com volumes tão diferentes competindo pelas mesmas Globals pesam na busca.

0
Heloisa Paiva  Jun. 4 to Aziz Cotrim

Show, Aziz!! Fico feliz que tenha ajudado. 

Seja muito bem vindo à comunidade e pode contar comigo nessa jornada!

0
Aziz Cotrim  22 h atrás to Heloisa Paiva

Muito obrigado Heloisa

0