No Dynamics AX existe um mecanismo de cache onde as tabelas podem ser configuradas para serem carregadas na memória e armazenadas em cache. Esse cache é limitado a uma certa quantidade de KB para evitar problemas de memória. A configuração de que estou falando é chamada entiretablecache
e carrega toda a tabela na memória assim que um único registro é solicitado.
Até recentemente contamos com alguns scripts para verificar o tamanho das tabelas que possuem essa configuração para ver se o tamanho da tabela está acima desse limite.
Agora, no entanto, a compactação entra em ação e coisas como sp_spaceused ou sys.allocation_units parecem relatar o espaço realmente usado pelos dados compactados.
Obviamente, o servidor de aplicativos está trabalhando com dados não compactados, portanto, o tamanho dos dados no disco no SQL Server é irrelevante. Eu preciso do tamanho real que os dados descompactados terão.
Conheço sp_estimate_data_compression_savings , mas como o nome diz, isso é apenas uma estimativa.
Eu preferiria ter o tamanho o mais correto possível.
A única maneira que eu conseguia pensar era algum SQL dinâmico complicado criando tabelas não compactadas com a mesma estrutura das tabelas compactadas, inserindo os dados compactados nessa tabela sombra e, em seguida, verificando o tamanho dessa tabela sombra.
Desnecessário dizer que isso é um pouco tedioso e demora um pouco para ser executado em um banco de dados de várias centenas de GB.
O Powershell pode ser uma opção, mas eu não gostaria de iterar em todas as tabelas para executar um select *
nelas para verificar o tamanho no script, pois isso apenas inundaria o cache e provavelmente levaria muito tempo também.
Em suma, preciso de uma maneira de obter o tamanho de cada tabela, pois ela será descompactada e com fragmentação fora da equação conforme apresentada ao aplicativo, se for possível. Estou aberto a diferentes abordagens, T-SQL é o preferido, mas não me oponho ao Powershell ou outras abordagens criativas.
Suponha que o buffer no aplicativo seja o tamanho dos dados. Um bigint é sempre do tamanho de um bigint e um tipo de dados de caractere é de 2 bytes por caractere (unicode). Os dados BLOB também assumem o tamanho dos dados, um enum é basicamente um int e os dados numéricos são numéricos (38,12), datetime é o tamanho de um datetime. Além disso, não há NULL
valores, eles são armazenados como uma string vazia 1900-01-01
ou zero.
Não há documentação sobre como isso é implementado, mas as suposições são baseadas em alguns testes e nos scripts usados pelos PFEs e pela equipe de suporte (que também ignoram a compactação aparentemente, já que a verificação é construída no aplicativo e o aplicativo não pode dizer se os dados subjacentes estiverem compactados) que também verificam os tamanhos das tabelas. Este link, por exemplo, afirma:
Evite usar caches de tabela inteira para tabelas grandes (no AX 2009 com mais de 128 KB ou 16 páginas, no AX 2012 com a configuração do aplicativo 'tamanho do cache de tabela inteira' [padrão: 32 KB ou 4 páginas]) – mude para o cache de registro.
Embora o desejo por essas informações seja certamente compreensível, obter essas informações, especialmente no contexto do "correto quanto possível", é mais complicado do que todos esperam devido a suposições incorretas. Seja fazendo a ideia da tabela de sombra descompactada mencionada na pergunta ou a sugestão de @sp_BlitzErik em um comentário sobre como restaurar o banco de dados e descompactar lá para verificar, não deve ser assumido que o tamanho da tabela descompactada == o tamanho dos referidos dados na memória no servidor de aplicativos:
Todas as linhas da tabela estão sendo armazenadas em cache? Ou apenas dentro de um intervalo? A suposição aqui é que é tudo, e isso pode estar correto, mas achei que deveria pelo menos ser mencionado que esse pode não ser o caso (a menos que a documentação indique o contrário, mas esse é um ponto menor de qualquer maneira, só não queria que não seja mencionado).
A pergunta foi atualizada para informar: sim, todas as linhas estão sendo armazenadas em cache.
Sobrecarga da estrutura
Sobrecarga de página e linha no lado do banco de dados: quantas linhas cabem em uma página é determinada por muitos fatores que podem prejudicar as estimativas. Mesmo com um
FILLFACTOR
de 100 (ou 0), ainda é provável que haja algum espaço não utilizado na página devido ao fato de não ser suficiente para uma linha inteira. E isso além do cabeçalho da página. Além disso, se qualquer funcionalidade de isolamento de instantâneos estiver habilitada, haverá, acredito, 13 bytes extras por linha ocupados pelo número da versão, e isso prejudicará as estimativas. Há outras minúcias relacionadas ao tamanho real da linha (bitmap NULL, colunas de comprimento variável, etc), mas os itens mencionados até agora devem fazer o ponto.que tipo de coleção está sendo usada para armazenar os resultados em cache? Eu suponho que este é um aplicativo .NET, então é um
DataTable
? Uma lista genérica? Um dicionário classificado? Cada tipo de coleção tem uma quantidade diferente de overheard. Eu não esperaria que nenhuma das opções necessariamente espelhasse as sobrecargas de página e linha no lado do banco de dados, especialmente em escala (tenho certeza de que uma pequena quantidade de linha pode não ter vários o suficiente para importar, mas você não está procurando diferenças em centenas de bytes ou apenas alguns kB).CHAR
/VARCHAR
os dados são armazenados em 1 byte por caractere (ignorando os caracteres de byte duplo no momento).XML
é otimizado para não ocupar tanto espaço quanto a representação de texto implicaria. Esse tipo de dados cria um dicionário de nomes de elementos e atributos e substitui as referências reais a eles no documento por seus respectivos IDs (na verdade, meio legal). Caso contrário, os valores de string são todos UTF-16 (2 ou 4 bytes por "caractere"), assim comoNCHAR
/NVARCHAR
.DATETIME2
é entre 6 e 8 bytes.DECIMAL
está entre 5 e 17 bytes (dependendo da precisão).Strings (novamente, assumindo .NET) são sempre UTF-16. Não há otimização para strings de 8 bits, como o que
VARCHAR
é válido. MAS, strings também podem ser "internadas", que é uma cópia compartilhada que pode ser referenciada muitas vezes (mas não sei se isso funciona para strings em coleções ou, se sim, se funciona para todos os tipos de coleções).XML
pode ou não ser armazenado da mesma maneira na memória (terei que procurar isso).DateTime
é sempre 8 bytes (como T-SQLDATETIME
, mas não comoDATE
,TIME
, ouDATETIME2
).Decimal
é sempre 16 bytes .Tudo isso para dizer: não há praticamente nada que você possa fazer no lado do banco de dados para obter um tamanho de espaço de memória bastante preciso no lado do servidor de aplicativos. Você precisa encontrar uma maneira de interrogar o próprio servidor de aplicativos, depois de ser carregado com uma tabela específica, para saber o tamanho dela. E não tenho certeza se um depurador permitiria ver o tamanho do tempo de execução de uma coleção preenchida. Caso contrário, a única maneira de chegar perto seria percorrer todas as linhas de uma tabela, multiplicando cada coluna pelo tamanho .NET
INT
apropriado (por exemplo, =* 4
,VARCHAR
=DATALENGTH() * 2
,NVARCHAR
=DATALENGTH()
,XML
= ?, etc), mas isso ainda deixa a questão da sobrecarga da coleção mais cada elemento da coleção.Dada alguma nova definição na pergunta, provavelmente poderíamos fazer a seguinte consulta para chegar bem perto. E não importa se a tabela está compactada ou não, mas cabe a cada pessoa determinar se a verificação de todas as linhas é apropriada em Produção (talvez a partir de uma restauração ou fora do horário de pico):
Mas lembre-se, isso não leva em conta a sobrecarga do elemento de coleção ou coleção. E não tenho certeza se podemos obter esse valor sem um depurador (ou possivelmente algo como ILSpy, mas não estou recomendando isso, pois pode violar o EULA dependendo das leis locais).
Pela sua pergunta, parece que você tem um tamanho máximo de cache
S
e não deseja carregar tabelas no cache que excedam esse tamanho. Se isso for verdade, você não precisa saber o tamanho exato de cada tabela. Você só precisa saber se uma tabela é maior ou menor que o tamanho máximo do cacheS
. Esse é um problema significativamente mais fácil, dependendo das definições de coluna e das contagens de linhas de suas tabelas.Concordo com a ótima resposta de Solomon Rutzky em que olhar para dados descompactados não é o caminho a seguir e pode ser difícil encontrar uma boa aproximação para o tamanho real de uma tabela em cache. No entanto, vou trabalhar dentro da estrutura da pergunta e assumir que você pode desenvolver uma fórmula que seja próxima o suficiente com base nas definições de coluna para tipos de dados estáticos e no comprimento real de suas colunas dinâmicas.
Se você tiver esse mapeamento de tipos de dados para o tamanho do cache, poderá avaliar algumas tabelas sem nem olhar para os dados nelas:
sys.partitions
e calculando o tamanho da tabela usando definições de coluna.BIGINT
colunas pode ter o tamanho desses dados como 10000000 * (8+8+8+8+8) = 400 M bytes, o que pode ser maior que o limite de tamanho do cacheS
. Não importa se também tem um monte de colunas de string.BIGINT
coluna e umaNVARCHAR(20)
coluna não pode exceder 100 * (8 + 2 * 20) = 4800 bytes.S
, é extremamente improvável que caiba no cache. Você teria que fazer testes para descobrir se esse valor existe.Você pode ter que consultar os dados de tabelas que não se encaixam em nenhum dos critérios acima. Existem alguns truques que você pode usar para minimizar o impacto no desempenho disso. Eu diria que você tem duas prioridades concorrentes aqui: você valoriza a precisão, mas também não deseja verificar todos os dados em seu banco de dados. Pode ser possível adicionar algum tipo de buffer aos seus cálculos. Não sei se é mais aceitável excluir uma tabela que está um pouco abaixo do tamanho máximo de cache
S
ou incluir uma tabela que está um pouco acima do tamanho máximo de cache.Aqui estão algumas ideias para tornar as consultas que analisam os dados da tabela mais rápidas:
TABLESAMPLE
desde que o tamanho da amostra seja grande o suficiente.SUM()
que saia cedo com base no valor desse agregado. Eu só vi esse trabalho paraROW_NUMBER()
. Mas você pode escanear os primeiros 10% da tabela, salvar o tamanho dos dados calculados, escanear os próximos 10% e assim por diante. Para tabelas que são muito grandes para o cache, você pode economizar uma quantidade significativa de trabalho com essa abordagem saindo mais cedo.Percebo que não incluí nenhum código SQL nesta resposta. Deixe-me saber se seria útil escrever um código de demonstração para qualquer uma das ideias que discuti aqui.