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
- Por exemplo, se DEFAULTGLOBAL = "^Demo.Person" isso compila como:
- 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>
- Atribua as Globals por indireção Em Tempo de Execução compilado como:
- 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
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.
Show, Aziz!! Fico feliz que tenha ajudado.
Seja muito bem vindo à comunidade e pode contar comigo nessa jornada!
Muito obrigado Heloisa