Tarefa
Arquive todos, exceto um período contínuo de 13 meses, de um grupo de tabelas grandes. Os dados arquivados devem ser armazenados em outro banco de dados.
- O banco de dados está no modo de recuperação simples
- As tabelas têm 50 mil linhas a vários bilhões e, em alguns casos, ocupam centenas de GB cada.
- As tabelas atualmente não estão particionadas
- Cada tabela tem um índice clusterizado em uma coluna de data cada vez maior
- Cada tabela possui adicionalmente um índice não clusterizado
- Todas as alterações de dados nas tabelas são inserções
- O objetivo é minimizar o tempo de inatividade do banco de dados primário.
- O servidor é 2008 R2 Enterprise
A tabela "arquivo" terá cerca de 1,1 bilhão de linhas, a tabela "ao vivo" cerca de 400 milhões. Obviamente, a tabela de arquivo aumentará com o tempo, mas espero que a tabela ao vivo também aumente razoavelmente rápido. Digamos 50% nos próximos anos, pelo menos.
Eu tinha pensado nos bancos de dados estendidos do Azure, mas infelizmente estamos no 2008 R2 e provavelmente ficaremos lá por um tempo.
Plano atual
- Criar um novo banco de dados
- Crie novas tabelas particionadas por mês (usando a data modificada) no novo banco de dados.
- Mova os dados dos 12 a 13 meses mais recentes para as tabelas particionadas.
- Faça uma troca de renomeação dos dois bancos de dados
- Exclua os dados movidos do banco de dados agora "arquivado".
- Particione cada uma das tabelas no banco de dados "arquivo".
- Use trocas de partição para arquivar os dados no futuro.
- Percebo que terei que trocar os dados a serem arquivados, copiar essa tabela para o banco de dados de arquivo e, em seguida, trocá-la na tabela de arquivo. Isso é aceitável.
Problema: estou tentando mover os dados para as tabelas particionadas iniciais (na verdade, ainda estou fazendo uma prova de conceito nela). Estou tentando usar o TF 610 (conforme o Guia de desempenho de carregamento de dados ) e uma INSERT...SELECT
instrução para mover os dados inicialmente pensando que seriam minimamente registrados. Infelizmente, toda vez que tento, é totalmente logado.
Neste ponto, estou pensando que minha melhor aposta pode ser mover os dados usando um pacote SSIS. Estou tentando evitar isso, pois estou trabalhando com 200 tabelas e tudo o que posso fazer por script, posso gerar e executar facilmente.
Há algo que estou perdendo em meu plano geral e o SSIS é minha melhor aposta para mover os dados rapidamente e com uso mínimo do log (preocupações de espaço)?
Código de demonstração sem dados
-- Existing structure
USE [Audit]
GO
CREATE TABLE [dbo].[AuditTable](
[Col1] [bigint] NULL,
[Col2] [int] NULL,
[Col3] [int] NULL,
[Col4] [int] NULL,
[Col5] [int] NULL,
[Col6] [money] NULL,
[Modified] [datetime] NULL,
[ModifiedBy] [varchar](50) NULL,
[ModifiedType] [char](1) NULL
);
-- ~1.4 bill rows, ~20% in the last year
CREATE CLUSTERED INDEX [AuditTable_Modified] ON [dbo].[AuditTable]
( [Modified] ASC )
GO
-- New DB & Code
USE Audit_New
GO
CREATE PARTITION FUNCTION ThirteenMonthPartFunction (datetime)
AS RANGE RIGHT FOR VALUES ('20150701', '20150801', '20150901', '20151001', '20151101', '20151201',
'20160101', '20160201', '20160301', '20160401', '20160501', '20160601',
'20160701')
CREATE PARTITION SCHEME ThirteenMonthPartScheme AS PARTITION ThirteenMonthPartFunction
ALL TO ( [PRIMARY] );
CREATE TABLE [dbo].[AuditTable](
[Col1] [bigint] NULL,
[Col2] [int] NULL,
[Col3] [int] NULL,
[Col4] [int] NULL,
[Col5] [int] NULL,
[Col6] [money] NULL,
[Modified] [datetime] NULL,
[ModifiedBy] [varchar](50) NULL,
[ModifiedType] [char](1) NULL
) ON ThirteenMonthPartScheme (Modified)
GO
CREATE CLUSTERED INDEX [AuditTable_Modified] ON [dbo].[AuditTable]
(
[Modified] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON ThirteenMonthPartScheme (Modified)
GO
CREATE NONCLUSTERED INDEX [AuditTable_Col1_Col2_Col3_Col4_Modified] ON [dbo].[AuditTable]
(
[Col1] ASC,
[Col2] ASC,
[Col3] ASC,
[Col4] ASC,
[Modified] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON ThirteenMonthPartScheme (Modified)
GO
Mover código
USE Audit_New
GO
DBCC TRACEON(610);
INSERT INTO AuditTable
SELECT * FROM Audit.dbo.AuditTable
WHERE Modified >= '6/1/2015'
ORDER BY Modified
Por que você não está obtendo log mínimo?
Achei o Data Loading Performance Guide , ao qual você faz referência, um recurso extremamente valioso. No entanto, também não é 100% abrangente e suspeito que a grade já seja complexa o suficiente para que o autor não tenha adicionado uma coluna
Table Partitioning
para quebrar as diferenças de comportamento, dependendo se a tabela que recebe as inserções é particionada. Como veremos mais adiante, o fato de a tabela já estar particionada parece inibir o log mínimo.Abordagem recomendada
Com base nas recomendações do Guia de desempenho de carregamento de dados (incluindo a seção "Carregamento em massa de uma tabela particionada"), bem como na ampla experiência em carregar tabelas particionadas com dezenas de bilhões de linhas, esta é a abordagem que eu recomendaria:
As diferenças em relação à sua abordagem original:
TABLOCK
um mês de cada vez, usando a alternância de partição para colocar os dados na tabela particionada.DELETE
para limpar a tabela antiga será totalmente registrada. Talvez você possaTRUNCATE
descartar a tabela e criar uma nova tabela de arquivo.Comparação de abordagens para mover o ano recente de dados
Para comparar abordagens em um período de tempo razoável em minha máquina, usei um
100MM row
conjunto de dados de teste que gerei e que segue seu esquema.Como você pode ver nos resultados abaixo, há um grande aumento de desempenho e redução nas gravações de log carregando dados em um heap usando a
TABLOCK
dica. Há um benefício adicional se isso for feito uma partição por vez. Também vale a pena notar que o método de uma partição por vez pode ser facilmente paralelizado ainda mais se você executar várias partições de uma vez. Dependendo do seu hardware, isso pode render um bom impulso; normalmente carregamos pelo menos quatro partições de uma vez em hardware de classe de servidor.Aqui está o script de teste completo .
Notas finais
Todos esses resultados dependem do seu hardware até certo ponto. No entanto, meus testes foram conduzidos em um laptop quad-core padrão com unidade de disco giratória. É provável que as cargas de dados sejam muito mais rápidas se você estiver usando um servidor decente que não tenha muitas outras cargas no momento em que estiver conduzindo esse processo.
Por exemplo, executei a abordagem recomendada em um servidor de desenvolvimento real (Dell R720) e vi uma redução para
76 seconds
(156 seconds
no meu laptop). Curiosamente, a abordagem original de inserção em uma tabela particionada não experimentou a mesma melhoria e ainda ocupou apenas12 minutes
o servidor de desenvolvimento. Presumivelmente, isso ocorre porque esse padrão gera um plano de execução serial e um único processador em meu laptop pode corresponder a um único processador no servidor de desenvolvimento.Este pode ser um bom candidato para Biml. Uma abordagem seria criar um modelo reutilizável que migraria dados para uma única tabela em pequenos intervalos de datas com um contêiner For Each. O Biml percorreria sua coleção de tabelas para criar pacotes idênticos para cada tabela qualificada. Andy Leonard tem uma introdução em sua série Stairway .
Talvez, em vez de criar o novo banco de dados, restaure o banco de dados real para um novo banco de dados e exclua os dados mais recentes de 12 a 13 meses. Em seguida, em seu banco de dados real, exclua os dados que não estão contidos na área de arquivo recém-criada. Se grandes exclusões forem um problema, talvez você possa excluir apenas 10K ou conjuntos maiores por meio de script para fazer isso.
Suas tarefas de particionamento não parecem sofrer interferência e parecem ser aplicáveis a qualquer um dos bancos de dados após suas exclusões.