Qlik Sense • Performance • Otimização

Performance Tuning Qlik Sense:
Diagnóstico e Otimização de Ponta a Ponta

80% dos problemas de lentidão têm origem no modelo de dados. O resto é script, expressão e arquitetura de objeto. Aqui está o mapa completo com scripts reais.

Se está lento, é diagnóstico antes de otimização.
0% dos problemas no modelo de dados
0x ganho com incremental load
0% memória economizável com DROP FIELDS
Role para baixo
Por Onde Começar

Diagnóstico Antes de Otimização

Antes de qualquer mudança, você precisa saber onde está o gargalo. Qlik Sense lento pode ter origem em quatro lugares distintos - e a solução para cada um é diferente.

1
Modelo de dados Synthetic keys ocultos, campos desnecessários em RAM, tabelas não normalizadas corretamente, NULLs não tratados. Causa a maioria dos problemas de reload lento e consumo de memória excessivo.
2
Script de carga Ausência de camada QVD, reloads completos onde deveria ser incremental, ordem ineficiente de LOADs, joins desnecessários no script. Impacta diretamente o tempo de reload.
3
Expressões e gráficos IF() onde deveria ser Set Analysis, AGGR() mal aplicado, cálculos complexos repetidos sem uso de variáveis, sheets sobrecarregados com objetos demais.
4
Infraestrutura e configuração Sizing incorreto de servidor, número excessivo de usuários simultâneos sem throttling, ausência de On-Demand App Generation para datasets grandes.

"Otimizar expressão em um app com synthetic key não resolvido é pintar a parede de uma casa com fundação rachada."

Como Ler o Log de Reload para Diagnóstico

O primeiro passo de qualquer tuning é ler o log de reload com atenção. No Qlik Cloud, acesse: Data Integration → Spaces → [app] → Reload History.

Estrutura do log de reload - o que observar
0000   Starting reload...
0001   LOAD statement executed: 2.450.000 rows in 4.2s      ← tempo por tabela
0002   Synthetic key warning: [OrderID, ProductID]          ← ALERTA CRÍTICO
0003   Store INTO vendas.qvd: 2.450.000 rows in 1.8s
0004   LOAD FROM vendas.qvd: 2.450.000 rows in 0.9s
...
0089   Finished reload. Duration: 00:04:32
       RAM peak: 8.4 GB                                     ← consumo de memória
       Fields: 847                                          ← muitos campos = problema

Synthetic key warning no log é o sinal mais claro de problema de modelo. Mais de 200 campos por app é outro sinal de alerta - provavelmente há campos desnecessários em memória.

1
Modelo de Dados
Synthetic keys, star schema, DROP FIELDS e NULLs

Synthetic Keys: Detecção e Correção

Synthetic keys são criados automaticamente pelo Qlik quando duas ou mais tabelas compartilham mais de um campo com o mesmo nome. Cada synthetic key gera uma tabela intermediária invisível que consome memória e degrada performance de associação.

Problema - cria synthetic key
// Pedidos e Produtos compartilham
// OrderID E ProductID - synthetic key!
Pedidos:
LOAD OrderID, ProductID,
     CustomerID, Amount
FROM pedidos.qvd (qvd);

Produtos:
LOAD ProductID, OrderID,
     ProductName, Category
FROM produtos.qvd (qvd);
Solução - alias para desambiguar
// Renomear o campo duplicado
// que não deve ser chave de join
Pedidos:
LOAD OrderID, ProductID,
     CustomerID, Amount
FROM pedidos.qvd (qvd);

Produtos:
LOAD ProductID,
     OrderID AS PedidoRef,  // alias
     ProductName, Category
FROM produtos.qvd (qvd);

DROP FIELDS - Libere Memória Imediatamente

Todo campo carregado no script ocupa memória RAM durante o reload e no app em uso. Campos que existem na fonte mas não são usados em nenhum gráfico ou expressão são desperdício puro.

Carrega tudo - memória desperdiçada
// LOAD * traz 40+ campos da fonte
// incluindo campos técnicos que
// ninguém usa no dashboard
Vendas:
LOAD * FROM vendas.qvd (qvd);
Seletivo - RAM otimizada
// Carrega apenas o necessário
// ou usa DROP após LOAD *
Vendas:
LOAD VendaID, ClienteID,
     ProdutoID, Valor,
     DataVenda, Regiao
FROM vendas.qvd (qvd);

// Alternativa: DROP após LOAD *
DROP FIELDS ETLTimestamp,
            BatchID, InternalRef,
            LegacyCode;

Star Schema vs Snowflake - Impacto em Performance

O Qlik Sense é otimizado para star schema. Modelos snowflake (múltiplos níveis de tabelas de dimensão) aumentam o número de joins internos e degradam performance de associação e cálculo.

Snowflake - joins em cascata
// Fatos → Produto → Categoria → Setor
// Cada seleção atravessa 3 tabelas
Fatos → Produtos → Categorias → Setores

// Performance de associação:
// O(n) por nível de join adicional
Star schema - join direto
// Desnormalizar no script de carga
// em vez de manter snowflake
DimProduto:
LOAD p.ProductID, p.ProductName,
     c.CategoryName, s.SectorName
FROM produtos.qvd (qvd) AS p
JOIN categorias.qvd (qvd) AS c
  ON p.CategoryID = c.CategoryID
JOIN setores.qvd (qvd) AS s
  ON c.SectorID = s.SectorID;

NoConcatenate - Quando Usar

O Qlik concatena automaticamente tabelas com os mesmos campos. Quando isso não é desejado - por exemplo, duas tabelas de fatos independentes - use NoConcatenate explicitamente.

// Sem NoConcatenate: Qlik concatena
// VendasNacionais e VendasExportacao
// em uma só tabela - pode não ser o desejado
VendasNacionais:
LOAD ClienteID, Valor, DataVenda
FROM nacionais.qvd (qvd);

// Com NoConcatenate: tabelas separadas
// com associação via ClienteID
NoConcatenate
VendasExportacao:
LOAD ClienteID, Valor, DataVenda
FROM exportacao.qvd (qvd);
2
Script e Camada QVD
Incremental load, Buffer, Binary e ordem de execução

Incremental Load - O Padrão que Todo App em Produção Deveria Ter

Recarregar 10 milhões de linhas toda vez quando apenas 5.000 mudaram é o erro mais caro de performance em Qlik. O padrão de incremental load resolve isso com três blocos: detectar delta, carregar delta, mesclar com QVD existente.

Padrão completo de Incremental Load com QVD
// BLOCO 1: Detectar a última data de carga
LET vUltimaCarga = Peek('DataModificacao', -1, 'MaxData');

// BLOCO 2: Carregar delta ou full conforme necessário
IF IsNull(vUltimaCarga) OR NOT QvdNoOfRecords('lib://QVDs/vendas.qvd') > 0 THEN
    // Primeira carga - full load
    Vendas_Delta:
    LOAD VendaID, ClienteID, ProdutoID,
         Valor, DataVenda, DataModificacao
    FROM [lib://DataSource/vendas.csv]
    (txt, utf8, embedded labels, delimiter is ',');
ELSE
    // Carga incremental - apenas registros novos/alterados
    Vendas_Delta:
    LOAD VendaID, ClienteID, ProdutoID,
         Valor, DataVenda, DataModificacao
    FROM [lib://DataSource/vendas.csv]
    (txt, utf8, embedded labels, delimiter is ',')
    WHERE DataModificacao >= '$(vUltimaCarga)';
END IF

// BLOCO 3: Mesclar delta com QVD existente
// Carrega o QVD excluindo registros que existem no delta
Vendas:
LOAD * FROM [lib://QVDs/vendas.qvd] (qvd)
WHERE NOT EXISTS(VendaID);

// Adiciona o delta
CONCATENATE(Vendas)
LOAD * RESIDENT Vendas_Delta;

DROP TABLE Vendas_Delta;

// Salva QVD atualizado
STORE Vendas INTO [lib://QVDs/vendas.qvd] (qvd);

// Registra timestamp da carga
MaxData:
LOAD Max(DataModificacao) AS DataModificacao RESIDENT Vendas;

Atenção: o campo de controle (DataModificacao) precisa ser confiável na fonte. Se a fonte não atualiza esse campo corretamente em updates, o incremental load vai perder registros alterados. Valide com a equipe de dados antes de implementar.

BUFFER LOAD - Cache Automático de Fontes Lentas

Para tabelas de dimensão que raramente mudam (produtos, hierarquias, tabelas de configuração), o Buffer cria um QVD automático e só recarrega da fonte quando o buffer expira ou a fonte muda.

// Buffer com expiração de 7 dias
// Qlik verifica se o QVD de buffer é mais recente
// que 7 dias. Se sim, usa o cache. Se não, recarrega.
Buffer (stale [7 days])
DimProduto:
LOAD ProductID, ProductName,
     Category, SubCategory,
     UnitCost, Supplier
FROM [lib://MasterData/produtos.csv]
(txt, utf8, embedded labels, delimiter is ',');

// Para dimensões que mudam raramente:
// hierarquia geográfica, tabela de metas anuais,
// plano de contas, tabela de câmbio histórica

BINARY LOAD - Reutilizar Modelo de Outro App

Se você tem um app "base" com o modelo de dados completo e vários apps analíticos em cima dele, o Binary carrega o modelo completo sem reprocessar o script.

// Carrega o modelo completo do app base
// sem re-executar o script de ETL
Binary [lib://AppBase/ModeloVendas.qvf];

// A partir daqui, todas as tabelas do app base
// estão disponíveis. Adicione apenas o que é
// específico deste app analítico.
Metas:
LOAD Regiao, Produto, MetaValor, AnoMeta
FROM [lib://Planning/metas_2025.xlsx]
(ooxml, embedded labels, table is Sheet1);

Ordem de LOAD - Joins e Resident

A ordem de execução do script impacta diretamente a performance. Tabelas maiores devem ser carregadas primeiro quando são usadas como base de join. Evite RESIDENT em loops desnecessários.

Ordem ineficiente
// Carrega dimensão pequena primeiro
// depois faz join com fatos grandes
DimCliente:
LOAD * FROM clientes.qvd (qvd);  // 5k rows

// Join com fatos - Qlik precisa
// expandir dimensão para cada fato
Fatos:
LOAD * FROM vendas.qvd (qvd)     // 10M rows
WHERE EXISTS(ClienteID);
Ordem eficiente
// Carrega fatos primeiro
Fatos:
LOAD VendaID, ClienteID, Valor
FROM vendas.qvd (qvd);  // 10M rows

// Dimensão em seguida - Qlik associa
// por chave sem re-scan da tabela grande
DimCliente:
LOAD ClienteID, NomeCliente, Segmento
FROM clientes.qvd (qvd);  // 5k rows
3
Expressões e Visualizações
Set Analysis vs IF, AGGR, variáveis e carga de sheet

Set Analysis vs IF() - A Diferença que Mais Impacta

Esta é a otimização de expressão com maior impacto. IF() no Qlik avalia condição linha por linha em toda a tabela. Set Analysis filtra o conjunto antes da agregação - o que é dramaticamente mais eficiente em datasets grandes.

IF() - avalia linha por linha
// Para 10M de linhas, esta expressão
// avalia a condição 10M vezes
Sum(IF(Categoria = 'Eletrônicos',
    Valor, 0))

// Ainda pior: IF aninhado
Sum(IF(Ano = 2024,
    IF(Regiao = 'Sul', Valor, 0),
0))
Set Analysis - filtra antes de agregar
// Filtra o conjunto antes de somar
// Muito mais eficiente em qualquer volume
Sum({<Categoria={'Eletrônicos'}>} Valor)

// Múltiplos filtros no mesmo set
Sum({<Ano={2024},
     Regiao={'Sul'}>} Valor)

// Regra: sempre prefira Set Analysis
// a IF() em funções de agregação

AGGR() - Quando Usar e Quando Evitar

AGGR() cria uma tabela virtual em tempo de execução para cada combinação de dimensões especificadas. É poderoso mas caro. Use com critério - e considere pré-calcular no script quando a expressão for estática.

AGGR() em expressão de chart - pesado
// Recalcula para cada renderização
// do gráfico - muito custoso
Avg(AGGR(
    Sum(Valor),
    ClienteID, MesAno
))

// Se o gráfico tem 50k clientes x 24 meses
// = 1.2M células calculadas a cada render
Pré-calculado no script - rápido
// Calcula uma vez no reload,
// armazena em QVD, usa no gráfico
VendasMensaisCliente:
LOAD ClienteID, MesAno,
     Sum(Valor) AS ValorMensal
FROM vendas.qvd (qvd)
GROUP BY ClienteID, MesAno;

// No gráfico: simplesmente
Avg(ValorMensal)  // instantâneo

Variáveis para Expressões Reutilizadas

Se a mesma expressão complexa aparece em múltiplos gráficos, calcular como variável evita recálculo redundante e facilita manutenção.

// No script: define variáveis de expressão
LET vVendasAno    = "Sum({<Ano={$(=Max(Ano))}>} Valor)";
LET vVendasAnoAnt = "Sum({<Ano={$(=Max(Ano)-1)}>} Valor)";
LET vCrescimento  = "($(vVendasAno) - $(vVendasAnoAnt)) / $(vVendasAnoAnt)";

// Nos gráficos: usa a variável
// - expressão calculada uma vez, reutilizada em N objetos
$(vCrescimento)   // em vez de repetir a expressão completa

Carga de Sheet - Objetos por Página

Cada objeto em um sheet executa suas expressões ao ser renderizado. Sheets com muitos objetos visíveis simultâneos impactam a experiência do usuário mesmo com modelo e script otimizados.

Always One Selected Value - Armadilha de Performance

Sem contexto de seleção - calcula tudo
// KPI sem filtro de ano
// avalia todos os registros históricos
Sum(Valor)  // 10 anos de dados = 50M linhas
Com contexto - cálculo focado
// Use variável de controle de estado
// ou Always One Selected Value no campo Ano
// para garantir contexto mínimo
Sum({<Ano={$(=Max(Ano))}>} Valor)

// Ou configure o campo Ano com
// "Always one selected value" no app props
4
Profiling e Monitoramento
Ferramentas para medir antes e depois de otimizar

Qlik Cloud: App Overview e Performance Monitor

No Qlik Cloud, acesse o App Overview de qualquer app para visualizar o footprint de memória por tabela, número de campos e distribuição de valores únicos - os dados que revelam onde está o problema.

Engine API - consulta de metadata de memória via REST
# Consulta tabelas do app via Qlik Engine API (WebSocket)
# Útil para automação de diagnóstico em múltiplos apps

POST https://SEU_TENANT.us.qlikcloud.com/api/v1/apps/APP_ID/evaluations
Authorization: Bearer ACCESS_TOKEN
Content-Type: application/json

{
  "qExpression": {
    "qv": "=FieldValueCount('ClienteID')"
  }
}

# Para diagnóstico manual: no Sense hub, clique no app
# → About → RAM usage por tabela
# Foco em: tabelas com RAM desproporcional ao número de linhas
# (indica campos com alta cardinalidade desnecessária)

Script Timing - Identifique o Gargalo de Reload

Adicione marcadores de tempo no script para identificar qual seção está consumindo mais tempo durante o reload.

// Marcadores de tempo no script
LET vT0 = Now();
TRACE === INÍCIO CARGA VENDAS ===;

Vendas:
LOAD * FROM [lib://QVDs/vendas.qvd] (qvd);

LET vT1 = Now();
TRACE === VENDAS: $(Interval(vT1 - vT0, 'mm:ss')) ===;

// Se uma seção demora muito,
// investigue: tamanho do arquivo,
// ausência de QVD, join complexo

LET vT2 = Now();
TRACE === INÍCIO CARGA CLIENTES ===;

DimCliente:
LOAD * FROM [lib://QVDs/clientes.qvd] (qvd);

TRACE === CLIENTES: $(Interval(Now() - vT2, 'mm:ss')) ===;

Checklist de Tuning - Auditoria Completa

Performance não é uma feature que se adiciona depois. É uma decisão de arquitetura que começa no modelo de dados. Meça antes, otimize com dados, meça depois.

Qual é o gargalo no seu app Qlik?

Modelo com synthetic key? Script sem camada QVD? Expressões IF() em cascata? Conta nos comentários qual é o seu cenário - já passei por todos esses em produção e posso ajudar a priorizar o diagnóstico.

E se este conteúdo foi útil, salva para usar como referência durante o próximo tuning.

Conectar com Rafael

#QlikSense #Qlik #PerformanceTuning #BusinessIntelligence #BI #DataEngineering #QlikCloud #DataArchitecture #Analytics