segunda-feira, 19 de janeiro de 2015

Erro 0x80070003 ao executar Restore-SPSite

Na execução de Restore-SPSite para restaurar nackup de um site em outro servidor ocorreu o serguinte erro:

Restore-SPSite : <nativehr>0x80070003</nativehr><nativestack></nativestack>

No log do SharePoint, procurando pelo nome do arquivo de backup, encontrei o seguinte erro:

Could not deserialize site from C:\TEMP\arquivo_backup.bak . Microsoft.SharePoint.SPException: Schema version of backup 15.0.4649.1000 does not match current schema version 15.0.4569.1000    

Causa do erro é que o servidor de origem está com versão do SharePoint (15.0.4649.1000) mais atual que o servidor de destino (15.0.4569.1000).

Como resolver:
  1. Verificar qual o update/service pack deve ser instalado. Em http://blogs.technet.com/b/steve_chen/archive/2013/03/26/sharepoint-2013-build-numbers-and-cu-39-s.aspx?pi13610=3#Build há uma tabela listando as atualizações, números de versão e links para download dos mesmos.
  2. Baixar e instalar a atualização.
  3. Executar o "SharePoint Product and Configuration Wizard", sem mudar nada, ou o número de versão não será atualizado. 
Nem sempre este último passo é necessário; para verificar, rode o seguinte em uma janela SharePoint Management Shell: 
(Get-SPServer -Identity nome_servidor).NeedsUpgrade

Se o retorno for "True", o Wizard deve ser executado. Confira o resultado executando

(Get-SPFarm).BuildNumber

Se o número de versão não for o esperado após o upgrade e execução do Wizard, dê um boot no servidor e cheque novamente.

segunda-feira, 25 de agosto de 2014

Cmdlets do Reporting Services não reconhecidas na Shell de Gerenciamento do SharePoint

Erro: "Install-SPRSService : The term 'Install-SPRSService' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again."

Soluções:
  1. Você não está em uma janela Windows PowerShell "comum" em vez de uma janela "Shell de Gerenciamento do SharePoint", ou o Reporting Services em modo integrado SharePoint não foi instalado ("Install Reporting Services SharePoint Mode for SharePoint 2013").
  2. O Service Pack 1 para SQL Server 2012 não foi instalado no Reporting Services. De acordo com "Software requirements for business intelligence in SharePoint Server 2013", o SP1 é necessário para Reporting Services em modo integrado no SharePoint 2013.

quinta-feira, 10 de abril de 2014

Como instalar os templates de projeto para SharePoint 2013 no Visual Studio 2012


  1. Baixe e instale o Web Platform Installer (Web PI): http://www.microsoft.com/web/downloads/platform.aspx.
  2. Feche as instâncias em execução do Visual Stuidio ou o setup irá falhar.
  3. No Web PI, selecione e instale o item "Microsoft Office Developer Tools for Visual Studio 2012". 
Depois da instalação, os templates estarão disponíveis na opção "New | Project", em "Installed | Templates | <language> | Office/SharePoint | SharePoint Solutions".

segunda-feira, 24 de março de 2014

SharePoint 2013: Configuração da sincronização de perfis de usuário

Na instalação default do SharePoint 2013, o serviço de sincronização de perfis de usuário não está configurada, então os alertas não funcionam porque os usuários não possuem endereço de e-mail para o qual o SharePoint possa enviar as mensagens de alerta. Supondo que seus endereços de e-mail estejam no AD, para configurar a sincronização de perfis de usuários faça o seguinte:
  1. Seguir o artigo ​"Synchronize user and group profiles in SharePoint Server 2013" (http://technet.microsoft.com/en-us/library/ee721049(v=office.15).aspx). Atenção para o detalhe de colocar a conta usada para a sincronização como Farm Administrator e administradora da máquina local (servidor SharePoint no qual o serviço de sincronização vai rodar).
  2. Usar o ADSI Edit no DC para dar direito de "Replicate Directory Changes" no container do AD que será replicado para o SharePoint.
  3. Meu domínio de teste não tinha Exchange, então a propriedade proxyAddress no AD não estava sendo preenchida. O SharePoint pega o e-mail dessa propriedade, então o e-mail no SharePoint só foi preenchido após trocar o mapeamento da propriedade "Work Email" do SharePoint de proxyAddress para mail (que havia sido preenchido manualmente no AD), e realizar uma sincronização full (uma incremental não trouxe os valores).
Falô,

GB

domingo, 2 de fevereiro de 2014

Visual Studio 2013 e Microsoft.SqlServer.Management.Sdk.Sfc

Erro "Could not file assembly Microsoft.SqlServer.Management.Sdk.Sfc, version ..." ao tentar criar nova conexão de dados no Visual Studio 2013.

Por algum motivo o SQL Server Management Objects (SMO) não foi instalado corretamente. O SMO pode ser baixado do site da Microsoft, mas como ele é parte do SQL Server Feature Pack, é isto que você tem que procurar. Na página de download do Feature Pack não há um único link para download; você tem que selecionar quais "partes" do Feature Pack você quer instalar. O SMO é o arquivo "SharedManagementObjects.msi". Você também deve instalar o CLR Types (arquivo "SQLSysClrTypes.msi"), que é usado pelo MSO.

A versão do SMO a instalar depende da versão mostrada na mensagem de erro:

Falô,

GB

quarta-feira, 13 de novembro de 2013

Como salvar um formulário InfoPath em uma subpasta de uma biblioteca SharePoint

Recentemente precisei salvar um formulário InfoPath 2007 ( :-P ) no seu local original, que era uma subpasta em uma biblioteca de formulários em um SharePoint (2007 tb, :-P ). Como o nome da pasta era gerada dinamicamente no momento da criação do formulário, baseado em um dado que não está presente no mesmo, não há como (pelo menos eu não consegui) criar uma conexão de dados para submeter o formulário para seu local de armazenamento.

A única solução que encontrei foi criar um campo no formulário que armazenasse sua URL, de forma a usar este valor na hora de salvar o formulário. Mas como o formulário está rodando no SharePoint, e é um item de uma lista, o modelo de objetos server do SharePoint está disponível no code-behind do formulário. Usando SPWeb.GetListItem() para acessar o item da lista correspondente ao formulário, as propriedades deste item fornecem a URL do formulário no SharePoint.

     public void FormEvents_Submit(object sender, SubmitEventArgs e)
     {
         // Recupera referência ao formulário na biblioteca SharePoint baseado na sua URL
         SPListItem item = SPContext.Current.Web.GetListItem(this.Uri);
         // Salva o formulário. No código a seguir, a conexão gera o nome do arquivo, mas se o nome
         // deve ser gerado em run-time, pode ser setado através de FileSubmitConnection.FileName
         FileSubmitConnection conexaoSP = (FileSubmitConnection)this.DataConnections["SalvarArquivo"];
         conexaoSP.FolderUrl = item.Web.Url + item.File.ParentFolder.ServerRelativeUrl;
         conexaoSP.Execute();
         e.CancelableArgs.Cancel = false; // Informa o InfoPath que o submit foi ok
     }

sexta-feira, 2 de agosto de 2013

Novidades do Hyper-V no Windows 8

  1. Tem Hyper-V no Windows 8! (no Windows 7 era só o Virtual PC).
  2. Como no Windows 2008 R2, o Hyper-V do Windows 8 suporta VM's de 64 bits (o Virtual PC só suporta VM's de 32 bits, o que é bem restritivo - por exemplo, o SharePoint 2010 não tem versão em 32 bits).
  3. Vantagens em relação ao Hyper-V do W2008 R2:
    - Suporta stand-by da máquina host (no Window 2008, ou você tem o Hyper-V, ou você consegue habilitar o stand-by; um ou o outro, não os dois)
    - Suporta nativamente conexões de VM's a redes externas WiFi (no Windows 2008, nativamente, as VM's só podiam se conectar a redes cabeadas - apesar de existirem "formas alternativas" de se fazer isto).
  4. Novo recurso: Virtaul SAN Manager. Não estudei ainda, mas parece ser suporte a criação de storages virtuais. 
Alguns recursos são novos e outros já eram possíveis de se implementar mas foram incorporados ao produto. De qualquer forma, esta versão que é incluída com o Windows 8 é perfeita para quem trabalha com TI e tem necessidade de criação de ambientes virtuais para desenvolvimento e testes em uma máquina de trabalho.

segunda-feira, 13 de maio de 2013

Assim é que é divertido ;-)

Erro na configuração da integração entre o TFS 2012 e System Center Virtual Machine Manager 2012 pra testes com o Lab Management. Vou procurar no Google e retornam 3 resultados:


Assim que é divertido... ;-)

sexta-feira, 26 de abril de 2013

Arquitetura de Alta Disponibilidade para o TFS

O esquema a seguir foi derivado de uma implementação de alta disponibilidade que fizemos para o TFS. Tem umas escolhas de arquitetura legais, estão comentadas a seguir:



  • Usamos máquinas virtuais para hospedar os servidores. Isto permite diminuir o número de hosts físicos a gerenciar.
  • Usamos NLB para o TFS, o SharePoint e o Reporting Services, e Failover Cluster Feature para o SQL Server (Database Engine) e o Analysis Services. Estas são as tecnologias de alta disponibilidade recomendadas pela Microsoft para cada um dos produtos citados.
  • A distribuição dos nós de cada cluster (NLB ou Failover) em hosts físicos distintos permite que os serviços sobrevivam à queda de um dos hosts.
  • Para os produtos em NLB, além da alta disponibilidade ainda há a vantagem da distribuição de carga entre os servidores. Além disto, o cluster NLB detecta automaticamente o retorno de um servidor que havia caído, reincluindo-o no processo de distribuição de carga.
  • No caso dos produtos em cluster, como o Failover Cluster do Windows não suporta modo "ativo-ativo", no qual ambas as máquinas respondem a requisições, colocamos a máquina que é o nó ativo do cluster para o Database Engine como sendo o nó passivo do cluster para Analysis Service, e vice-versa. Isto evita a existência de uma máquina "parruda" (como tem que ser um servidor SQL Server) e "parada" (o nó passivo), pois ambas respondem a requisições.
  • Antes tínhamos uma instância do SQL Server para o TFS e outra para o SharePoint; agora ambos usam a mesma instância. Isto permite um melhor uso de recursos, p.e. RAM não usada por um produto pode ser usada por outro, o que não acontece em instâncias separadas.
  • O uso de um alias DNS para os clusters NLB tornam manutenções, movimentações e substituições de servidores invisíveis às máquinas dos desenvolvedores. Aliás o uso destes alias para é uma boa mesmo se você não tem um NLB por trás, exatamente pelas razões apresentadas.
Projetinho simplezinho mas bonitinho. Vai ficar mais bonito quando a gente jogar o Lab Management aí dentro.

quinta-feira, 29 de novembro de 2012

Memory Grant em VARCHAR(8000)

Este é um dos posts sobre os exemplos apresentados na palestra sobre modelagem no 24 Hours of PASS - PT. Você pode ver a série de posts em Impactos da Modelagem na Performance do SQL Server.

Esta foi uma dica do Fabiano para colocarmos como exemplo na palestra.

O Otimizador de Consultas (QO - Query Optimizer) do SQL Server estima quanto de memória a execução de uma consulta necessitará. Isto é feito, p.e., para que operações de intenso acesso aos dados, como ordenações e agrupamentos, possam trabalhar com estes dados em memória, evitando o acesso a disco, o que causaria uma acentuada queda na performance. Para estimar esta quantidade de RAM, o QO leva em conta uma série de fatores, dentre os quais pode entrar o tamanho dos dados que serão trabalhados. Se uma tabela é declarada com muitas colunas varchar(8000), o SQL Server não sabe quanto espaço é usado em cada linha para esta coluna, então toma uma média - 4.000 bytes de espaço usado por linha. Então a estimativa do QO pode ser muito maior do que o tamanho da massa de dados - decorrente de uma modelagem "preguiçosa". Isto incorre em maior uso de memória e, em cenários de pressão de memória no servidor, em contenção ocorrendo entre as consultas - consultas bloqueadas esperando a liberação da memória estimada para sua execução.

O script a seguir demonstra isto. Copie ele para uma nova query no Management Studio (não esqueça de mudar para um banco de testes), vá executando os pedaços de código entre os comentários e veja os resultados. Qualquer dúvida posta uma pergunta!




/* ===============================================================================================================================
   Alocação errada de RAM devido a má modelagem das colunas varchar.
   O Query Optimizer (QO) para execução de determinadas consultas aloca RAM baseando-se no tamanho médio dos dados das colunas.
   No exemplo, ao usar varchar(8000), você estará informando ao QO que o tamanho médio destas colunas é 4000 bytes.
   Além disto, se o SQL Server está sob pressão de memória, pedidos de alocação de grandes quantias de RAM podem gerar bloqueios
   (waits) por liberação desta quantidade de memória, o que pode impactar severamente a performance de execução das queries.
   =============================================================================================================================== */

-- Vamos simular um SQL Server com pressão de memória

EXEC sp_configure 'max server memory'
EXEC sp_configure 'show advanced options', 1
reconfigure
EXEC sp_configure 'max server memory', 1024 -- 1GB de RAM para o SQL Server
reconfigure
GO

DROP TABLE TestMemoryGrant8000
CREATE TABLE TestMemoryGrant8000(ID  INT IDENTITY (1,1),
                             Col1 VarChar(8000) DEFAULT NEWID() PRIMARY KEY,
                             Col2 VarChar(8000) DEFAULT NEWID(),
                             Col3 VarChar(8000) DEFAULT NEWID(),
                             Col4 VarChar(8000) DEFAULT NEWID(),
                             Col5 VarChar(8000) DEFAULT NEWID(),
                             Col6 VarChar(8000) DEFAULT NEWID(),
                             Col7 VarChar(8000) DEFAULT NEWID(),
                             Col8 VarChar(8000) DEFAULT NEWID(),
                             Col9 VarChar(8000) DEFAULT NEWID(),
                             Col10 VarChar(8000) DEFAULT NEWID())
GO

INSERT INTO TestMemoryGrant8000 DEFAULT VALUES
GO 5000

-- Plano de execução: Memory Grant no SELECT dá 189MB. 
-- OBS: Tamanho de tabela nesse momento é 3MB (coluna page_count em sys.dm_db_index_physical_stats
SELECT * 
  FROM TestMemoryGrant8000
 ORDER BY Col4

SELECT * FROM sys.dm_db_index_physical_stats(
 DB_ID('Testes'), OBJECT_ID('TestMemoryGrant8000'), DEFAULT, DEFAULT, 'DETAILED')

-- Mesma estrutura, tabela com tamanhos "certos" nas colunas varchar

DROP TABLE TestMemoryGrant250
CREATE TABLE TestMemoryGrant250(ID  INT IDENTITY (1,1),
                             Col1 VarChar(250) DEFAULT NEWID() PRIMARY KEY,
                             Col2 VarChar(250) DEFAULT NEWID(),
                             Col3 VarChar(250) DEFAULT NEWID(),
                             Col4 VarChar(250) DEFAULT NEWID(),
                             Col5 VarChar(250) DEFAULT NEWID(),
                             Col6 VarChar(250) DEFAULT NEWID(),
                             Col7 VarChar(250) DEFAULT NEWID(),
                             Col8 VarChar(250) DEFAULT NEWID(),
                             Col9 VarChar(250) DEFAULT NEWID(),
                             Col10 VarChar(250) DEFAULT NEWID())
GO

INSERT INTO TestMemoryGrant250 DEFAULT VALUES
GO 5000

-- Plano de execução: Memory Grant no SELECT dá 8MB!!!
SELECT * 
  FROM TestMemoryGrant250
 ORDER BY Col4

-- Sintoma 2: Contenção por espaço em RAM

-- Executar SELECT’s no SQLQueryStress (http://www.datamanipulation.net/sqlquerystress/sqlquerystressdownload.asp)
-- com 100 threads e 2 comandos. Vai ocorrer um monte de wait do tipo RESOURCE_SEMAPHORE na tabela com campos grandes 
-- (não ocorre na tabela com campos pequenos)
-- Notar a diferença de tempo na execução (sensível mesmo em tabelas minúsculas como a do exemplo)
SELECT * FROM sys.dm_os_waiting_tasks 
where session_id >= 50
-- WAIT_TYPE = RESOURCE_SEMAPHORE 
-- "Occurs when a query memory request cannot be granted immediately due to other concurrent queries. 
-- High waits and wait times may indicate excessive number of concurrent queries, *or excessive memory request amounts*."
-- http://technet.microsoft.com/en-us/library/ms179984.aspx

TEXT x VARCHAR(MAX)

Este é um dos posts sobre os exemplos apresentados na palestra sobre modelagem no 24 Hours of PASS - PT. Você pode ver a série de posts em Impactos da Modelagem na Performance do SQL Server.

Este eu peguei emprestado pra palestra a partir de um post que o Luti colocou no site da Sr Nimbus, mas como minha didática é melhor que a dele, vou colocar o exemplo com meus comentários aqui. ;-)

Colunas TEXT e VARCHAR(MAX) armazenam objetos grandes, ou LOB's (Large Objects). Sintaticamente são iguais: uma coluna TEXT ou VARCHAR(MAX) armazena um valor string de até 2GB por registro. No entanto há uma diferença que pode impactar severamente na quantidade de IO realizada por uma consulta em uma tabela que contenha colunas destes tipos, e também na performance do Data Cache, um dos principais mecanismos de otimização do SQL Server, que é a área de memória que serve de buffer para as páginas lidas de disco. Tudo isto por uma "pequena" diferença na forma como o armazenamento dos dados LOB é feita, dependendo do tipo usado para declarar a coluna.

O script a seguir demonstra isto. Copie ele para uma nova query no Management Studio (não esqueça de mudar para um banco de testes), vá executando os pedaços de código entre os comentários e veja os resultados. Qualquer dúvida posta uma pergunta!




/* ===============================================================================================================================
   Comparação entre modos de armazenamento de dados para os tipos de dados "large data type values" (varchar(max), nvarchar(max) e
   varbinary(max) e "large object values" (text, ntext, image)
   Por default os "large data types" armazenam dados "in row", o que pode ocasinar muito IO desnecessário e mau uso do data cache.
   Este comportamento pode ser modificado através da opção de tabela "large value types out of row"
   http://msdn.microsoft.com/en-us/library/ms189087(v=sql.105).aspx
   =============================================================================================================================== */

-- Tabela com campo LOB armazenado em coluna VARCHAR(MAX)

DROP TABLE dbo.TabelaTEXT 
CREATE TABLE dbo.TabelaTEXT (
ID INT IDENTITY NOT NULL CONSTRAINT PK_TabelaTEXT PRIMARY KEY
, Nome VARCHAR(100) NOT NULL DEFAULT ('Sr. Nimbus')
, DataRegistro DATETIME2 NOT NULL DEFAULT(SYSDATETIME())
, Texto TEXT NOT NULL DEFAULT (REPLICATE('A', 2000))
)
GO

-- Mesma estrutura, exceto campo LOB que é armazenado em coluna TEXT

DROP TABLE dbo.TabelaVARMAX 
CREATE TABLE dbo.TabelaVARMAX (
ID INT IDENTITY NOT NULL CONSTRAINT PK_TabelaVARMAX PRIMARY KEY
, Nome VARCHAR(100) NOT NULL DEFAULT ('Sr. Nimbus')
, DataRegistro DATETIME2 NOT NULL DEFAULT(SYSDATETIME())
, Texto VARCHAR(MAX) NOT NULL DEFAULT (REPLICATE('A', 2000))
)
GO

-- Popula as 2 tabelas

INSERT INTO dbo.TabelaTEXT DEFAULT VALUES
INSERT INTO dbo.TabelaVARMAX DEFAULT VALUES
GO 10000

select count(*) from TabelaTEXT
select count(*) from TabelaVARMAX

-- Vamos verificar a quantidade de IO realizada pelas consultas abaixo

SET STATISTICS IO ON

SELECT ID, Nome FROM dbo.TabelaTEXT
SELECT ID, Nome FROM dbo.TabelaVARMAX
-- TabelaTEXT > TabelaVARMAX
-- Em TabelaTEXT a área de dados contém campos LOB
-- SELECT não requisitou estes campos!

SET STATISTICS IO OFF

-- Tamanho e localização dos dados nas tabelas

SELECT
 OBJECT_NAME(object_id) AS ObjectName ,
 AU.type ,
 AU.type_desc ,
 AU.container_id ,
 AU.filegroup_id ,
 AU.total_pages ,
 AU.used_pages ,
 AU.data_pages
FROM SYS.system_internals_allocation_units AS AU
  INNER JOIN SYS.Partitions AS P
ON AU.Container_id = P.Partition_id
WHERE Object_ID IN (object_id('TabelaTEXT'), object_id('TabelaVARMAX'))
ORDER BY object_id, type

-- Mudando o comportamento de VARCHAR(MAX): opção "large value types out of row"

EXEC sp_tableoption 'TabelaVARMAX', 'large value types out of row', 1
GO

-- Para dados existentes, armazenamento só muda quando linha é modificada

UPDATE dbo.TabelaVARMAX SET Texto = Texto

-- Rebuild do índice para compactar tabela

ALTER INDEX PK_TabelaVARMAX ON dbo.TabelaVARMAX REBUILD

-- Vamos verificar a quantidade de IO realizada pelas consultas abaixo

SET STATISTICS IO ON

SELECT ID, Nome FROM dbo.TabelaTEXT
SELECT ID, Nome FROM dbo.TabelaVARMAX
-- TabelaTEXT == TabelaVARMAX
-- Ambas agora usam armazenamento de LOB's out of row

SET STATISTICS IO OFF

IDENTITY na PK - Espaço e IO


Este é um dos posts sobre os exemplos apresentados na palestra sobre modelagem no 24 Hours of PASS - PT. Você pode ver a série de posts em Impactos da Modelagem na Performance do SQL Server.

O SQL Server cria automaticamente um índice para uma coluna declarada como chave primária (constraint PRIMARY KEY). Por default, este índice é do tipo clustered (se você não está familiarizado com os tipos de índice do SQL Server, veja este vídeo sobre o assunto [ainda não disponível]). Caso a coluna usada como chave do índice clustered seja "grande", isto irá aumentar o espaço de armazenamento da tabela, pois os outros índices (non-clustered) irão incorporar o valor da chave do índice clustered para referenciar cada linha da tabela. Se a coluna usada como chave do índice clustered for uma coluna passível de alteração, pode haver um aumento de IO nas operações que modifiquem o valor desta coluna, pois esta modificação deverá ser propagada para todos os índices non-clustered.

O script a seguir demonstra isto. Copie ele para uma nova query no Management Studio (não esqueça de mudar para um banco de testes), vá executando os pedaços de código entre os comentários e veja os resultados. Qualquer dúvida posta uma pergunta!



 
/* ===============================================================================================================================
   - Chave pequena: diminui espaço usado
   - Chave imutável: menos IO 
   Neste script criamos duas tabelas de teste, uma com chave pequena e imutável (int identity) e outra com chave "grande" e 
   passível de modificação (Email varchar(200)). Após incluir a mesma massa de testes nas duas, vemos que a tabela com chave identity 
   ocupa menos espaço que a tabela com chave natural. Ao realizar um UPDATE na coluna Email, vemos que o comando gera mais IO 
   quando esta coluna é parte da chave primária. Ambas as situações se devem ao fato de que os valores do índice clustered são
   usados pelos índices non-clustered (como especificado em http://msdn.microsoft.com/en-us/library/ms177484(v=sql.105).aspx)
   =============================================================================================================================== */


-- Tabela Pessoas_PKEMAIL: PK é Email - "grande" e passível de modificação 

IF EXISTS(SELECT * FROM sys.tables WHERE name = 'Pessoas_PKEMAIL') DROP TABLE Pessoas_PKEMAIL
CREATE TABLE Pessoas_PKEMAIL
(
 Codigo int not null identity UNIQUE,    -- Índice (non-clustered) é criado para reforçar constraint UNIQUE
 Email varchar(200) not null PRIMARY KEY,   -- Índice (clustered) é criado para reforçar constraint PK
 Nome varchar(50) not null, 
 Sobrenome varchar(50) not null,
 CPF char(11) not null UNIQUE,     -- Índice (non-clustered) é criado para reforçar constraint UNIQUE
 UF char(2)
)

CREATE INDEX IX_Pessoas_PKEMAIL_UF ON Pessoas_PKEMAIL(UF) -- Alguém ainda colocou um índice em UF (não foi culpa minha)
GO

-- Tabela Pessoas_PKIDENTITY: PK é Codigo - "pequena" e imutável

IF EXISTS(SELECT * FROM sys.tables WHERE name = 'Pessoas_PKIDENTITY') DROP TABLE Pessoas_PKIDENTITY
CREATE TABLE Pessoas_PKIDENTITY
(
 Codigo int not null identity PRIMARY KEY,   -- Índice (clustered) é criado para reforçar constraint PK
 Email varchar(200) not null UNIQUE,    -- Índice (non-clustered) é criado para reforçar constraint UNIQUE
 Nome varchar(50) not null, 
 Sobrenome varchar(50) not null,
 CPF char(11) not null UNIQUE,     -- Índice (non-clustered) é criado para reforçar constraint UNIQUE
 UF char(2)
)

CREATE INDEX IX_Pessoas_PKIDENTITY_UF ON Pessoas_PKIDENTITY(UF) -- Alguém ainda colocou um índice em UF (não foi culpa minha)
GO

-- Inclui os mesmos dados de teste em cada tabela

INSERT INTO Pessoas_PKEMAIL
SELECT  Email = p1.FirstName + '.' + p1.LastName + '@srnimbus.com.br', 
  Nome = p1.FirstName,
  Sobrenome = p1.LastName, 
  CPF = LEFT(NEWID(), 11),
  UF = LEFT(p1.FirstName,1) + LEFT(p1.LastName,1)
 FROM  
 (SELECT DISTINCT FirstName, LastName = isnull(MiddleName,'') + LastName
  FROM AdventureWorks2012.Person.Person) P1 

INSERT INTO Pessoas_PKIDENTITY
SELECT  Email = p1.FirstName + '.' + p1.LastName + '@srnimbus.com.br', 
  Nome = p1.FirstName,
  Sobrenome = p1.LastName, 
  CPF = LEFT(NEWID(), 11),
  UF = LEFT(p1.FirstName,1) + LEFT(p1.LastName,1)
 FROM  
 (SELECT DISTINCT FirstName, LastName = isnull(MiddleName,'') + LastName
  FROM AdventureWorks2012.Person.Person) P1 

GO

-- Quantidade de péginas por tabela, incluíndo os índices: coluna sys.dm_db_index_physical_stats().page_count
-- Veja que a tabela com chave IDENTITY ocupa menos espaço que a tabela com chave Email, pois os valores
-- chaves de cada registro são copiados para as entradas dos índices non-clustered.

SELECT name, SUM(page_count)
FROM sys.tables t INNER JOIN sys.dm_db_index_physical_stats(DB_ID('Testes'), NULL, DEFAULT, DEFAULT, 'DETAILED') ips
ON t.object_id = ips.object_id
WHERE t.name IN ('Pessoas_PKIDENTITY', 'Pessoas_PKEMAIL')
GROUP BY name

-- Quantidade de IO: logical reads - páginas acessadas do cache de dados. 
-- Veja que o UPDATE na tabela com PK Email consistentemente acessa mais páginas que o UPDATE na tabela com PK IDENTITY,
-- pois a atualização de uma coluna que é usada em um índice clustered deve ser propagada para todos os índices 
-- non-clustered.
-- (Obviamente que um UPDATE na coluna Codigo geraria mais IO, mas como esta coluna é "artificial", não há necessidade
--  de a aplicação atualizá-la, ao contrário da coluna Email.)

SET STATISTICS IO ON

UPDATE Pessoas_PKEMAIL SET Email = CAST(NEWID() as varchar(36)) + '@srnimbus.com.br' WHERE Codigo = 1
UPDATE Pessoas_PKIDENTITY SET Email = CAST(NEWID() as varchar(36)) + '@srnimbus.com.br' WHERE Codigo = 1

SET STATISTICS IO OFF

IDENTITY na PK - Fragmentação

Este é um dos posts sobre os exemplos apresentados na palestra sobre modelagem no 24 Hours of PASS - PT. Você pode ver a série de posts em Impactos da Modelagem na Performance do SQL Server.

Por default, no SQL Server uma coluna declarada como chave primária (constraint PRIMARY KEY) cria automaticamente um índice clustered (se você não está familiarizado com os tipos de índice do SQL Server, veja este vídeo sobre o assunto [ainda não disponível]). No índice clustered, como a ordem lógica dos registros em disco é mesma ordem da chave do índice, se os valores da chave nas linhas sendo inseridas são monotônicos (sempre crescente ou sempre decrescente) as inclusões não causam fragmentação na tabela. Se a chave não for monotônica a tabela sofrerá fragmentação.

O script a seguir demonstra isto. Copie ele para uma nova query no Management Studio (não esqueça de mudar para um banco de testes), vá executando os pedaços de código entre os comentários e veja os resultados. Qualquer dúvida posta uma pergunta!


  
  
/* ===============================================================================================================================
   1 - Chave não-monotônica: causa fragmentação    
   Neste script criamos uma tabela de testes (Pessoas) que tem uma PK na coluna EMail (varchar).
   Incluimos 8.000 linhas com valores de e-mails "aleatórios" (não ordenados), e vemos a fragmentação causada.
   Reconstruímos o índice base da PK e vemos a fragmentação ser eliminada pela reordenação dos registros e das páginas da tabela.
   Inserimos mais 8.000 linhas e novamente a tabela se fragmenta. 
   A PK não é monotônica.
   =============================================================================================================================== */

-- Criação de tabela com PK "natural" (E-mail)

DROP TABLE Pessoas
CREATE TABLE Pessoas
(
 Codigo int not null identity,
 Email varchar(200) not null,
 Nome varchar(50) not null, 
 Sobrenome varchar(50) not null,
 CPF char(11) not null,
 UF char(2)

 CONSTRAINT PK_Pessoas PRIMARY KEY (Email),
)
GO

-- Pega nome e sobrenome de AdventureWorks2012.Person.Person (download em http://msftdbprodsamples.codeplex.com/releases/view/93587) pra gerar dados de teste
-- O uso de um cursor na inserção é para simular uma aplicação transacional inserindo linhas na tabela
-- (Se usarmos um INSERT INTO ... SELECT ... o Query Optimizer ordena as linhas antes de fazer a inserção, e a fragmentação não ocorre)

-- Pega os 1os 8.000 registros do SELECT que gera os dados de teste e os insere na tabela

DECLARE @nome varchar(100), @sobrenome varchar(100)
DECLARE c CURSOR FOR 
 SELECT DISTINCT TOP 8000 FirstName, LastName = isnull(MiddleName,'') + LastName
 FROM AdventureWorks2012.Person.Person
 ORDER BY LastName ASC
OPEN c
FETCH NEXT FROM c INTO @nome, @sobrenome
WHILE @@FETCH_STATUS = 0
BEGIN
 INSERT INTO Pessoas VALUES (
  @nome + '.' + @sobrenome + '@srnimbus.com.br', -- Email
  @nome,      -- Nome
  @sobrenome,     -- Sobrenome
  LEFT(NEWID(), 11),    -- CPF
  LEFT(@nome,1) + LEFT(@sobrenome,1)  -- UF
 )
 FETCH NEXT FROM c INTO @nome, @sobrenome
END
CLOSE c
DEALLOCATE c
GO

SELECT COUNT(*) FROM Pessoas

-- PK EMail não é monotônica: fragmentação altíssima

DBCC SHOWCONTIG('Pessoas')
GO
-- Se reconstruirmos o índice a fragmentação é eliminada pela reordenação das páginas, mas isto é só temporário - novas
-- operações de inclusão e alteração irão fragmentar novamente a tabela (o que não ocorre numa PK IDENTITY)

ALTER INDEX PK_Pessoas ON Pessoas REBUILD
DBCC SHOWCONTIG('Pessoas')
GO

-- Inserção de mais 8.000 linhas fragmenta a tabela de novo

DECLARE @nome varchar(100), @sobrenome varchar(100)
DECLARE c CURSOR FOR 
 SELECT DISTINCT TOP 8000  FirstName, LastName = isnull(MiddleName,'') + LastName
 FROM AdventureWorks2012.Person.Person
 order by LastName DESC
OPEN c
FETCH NEXT FROM c INTO @nome, @sobrenome
WHILE @@FETCH_STATUS = 0
BEGIN
 INSERT INTO Pessoas VALUES (
  @nome + '.' + @sobrenome + '@srnimbus.com.br', -- Email
  @nome,      -- Nome
  @sobrenome,     -- Sobrenome
  LEFT(NEWID(), 11),    -- CPF
  LEFT(@nome,1) + LEFT(@sobrenome,1)  -- UF
 )
 FETCH NEXT FROM c INTO @nome, @sobrenome
END
CLOSE c
DEALLOCATE c
GO

DBCC SHOWCONTIG('Pessoas')




/* ===============================================================================================================================
   2 - Chave monotônica: não gera fragmentação.
   Neste script repetimos a sequencia do script anterior, com a diferença de que a PK agora é monotônica
   (uma coluna IDENTITY).
   Incluimos 8.000 linhas com valores de e-mails "aleatórios" (não ordenados), e não ocorre fragmentação, pois as chaves dos registros é 
   crescente.
   Inserimos mais 8.000 linhas e nenhuma fragmentação ocorre. 
   A PK é monotônica.
   =============================================================================================================================== */

-- Criação de tabela com PK artificial (Codigo - identity)

DROP TABLE Pessoas
CREATE TABLE Pessoas
(
 Codigo int not null identity,
 Email varchar(200) not null,
 Nome varchar(50) not null, 
 Sobrenome varchar(50) not null,
 CPF char(11) not null,
 UF char(2)

 CONSTRAINT PK_Pessoas PRIMARY KEY (Codigo),
)
GO

-- Pega os 1os 8.000 registros do SELECT que gera os dados de teste e os insere na tabela

DECLARE @nome varchar(100), @sobrenome varchar(100)
DECLARE c CURSOR FOR 
 SELECT DISTINCT TOP 8000 FirstName, LastName = isnull(MiddleName,'') + LastName
 FROM AdventureWorks2012.Person.Person
 ORDER BY LastName ASC
OPEN c
FETCH NEXT FROM c INTO @nome, @sobrenome
WHILE @@FETCH_STATUS = 0
BEGIN
 INSERT INTO Pessoas (Email, Nome, Sobrenome, CPF, UF)
 VALUES (
  @nome + '.' + @sobrenome + '@srnimbus.com.br', -- Email
  @nome,      -- Nome
  @sobrenome,     -- Sobrenome
  LEFT(NEWID(), 11),    -- CPF
  LEFT(@nome,1) + LEFT(@sobrenome,1)  -- UF
 )
 FETCH NEXT FROM c INTO @nome, @sobrenome
END
CLOSE c
DEALLOCATE c
GO

SELECT COUNT(*) FROM Pessoas

-- PK Codigo é monotônica: nenhuma fragmentação ocorre - inserção é sempre no "fim" da tabela

DBCC SHOWCONTIG('Pessoas')
GO

-- Inserção de mais 8.000 linhas: tabela se mantém compacta

DECLARE @nome varchar(100), @sobrenome varchar(100)
DECLARE c CURSOR FOR 
 SELECT DISTINCT TOP 8000  FirstName, LastName = isnull(MiddleName,'') + LastName
 FROM AdventureWorks2012.Person.Person
 order by LastName DESC
OPEN c
FETCH NEXT FROM c INTO @nome, @sobrenome
WHILE @@FETCH_STATUS = 0
BEGIN
 INSERT INTO Pessoas (Email, Nome, Sobrenome, CPF, UF)
 VALUES (
  @nome + '.' + @sobrenome + '@srnimbus.com.br', -- Email
  @nome,      -- Nome
  @sobrenome,     -- Sobrenome
  LEFT(NEWID(), 11),    -- CPF
  LEFT(@nome,1) + LEFT(@sobrenome,1)  -- UF
 )
 FETCH NEXT FROM c INTO @nome, @sobrenome
END
CLOSE c
DEALLOCATE c
GO

DBCC SHOWCONTIG('Pessoas')

Impactos da Modelagem na Performance do SQL Server

Oi Gente,

O pessoal da Sr. Nimbus apresentou 3 palestras no 1º dia do evento "24 Hours of PASS - Portuguese Edition". Minha palestra, juntamente com o Ivan, foi "Como (Não) Modelar Seu Banco de Dados". Nós buscamos mostrar situações nas quais uma modelagem pobre pode impactar severamente a performance do SQL Server, através do aumento de uso de recursos no armazenamento e na execução de consultas.

A palestra foi dividida em 3 "episódios": Chaves Primárias, Modelagem e o Otimizador, e Qual o Tamanho da Coluna?. Apresentei "Chaves Primárias" e "Qual o Tamanho da Coluna", e como os exemplos ficaram interessantes, irei publicá-los aqui em uma pequena série de posts:

  • Chaves Primárias:
    • Introdução aos tipos de índices no SQL Server [ainda não disponível]. Pré-requisito para os artigos sobre chaves primárias, que no SQL Server são baseadas por default em índices do tipo clustered.
    • IDENTITY na PK - Fragmentação. Uma chave não-monotônica pode causar fragmentação na sua tabela, o que aumenta o espaço de armazenamento e degrada o tempo de IO.
    • IDENTITY na PK - Espaço e IO. PK's "grandes" e passíveis de modificação incham a tabela e suas páginas de índice, e causam mais IO do que chaves "pequenas" e imutáveis.
  • Qual o Tamanho da Coluna?
    • TEXT x VARCHAR(MAX). Apesar de sintaticamente equivalentes, há uma característica de armazenamento que pode impactar (e muito) na performance de IO e no uso de RAM para consultas em tabelas que armazenam LOB's (Large Objects - informação > 8.000 bytes).
    • Memory Grant em varchar(8000). Cuidado com o espaço declarado para suas colunas de tamanho variável. Ela só ocupa o espaço necessário para o armazenamento dos dados, mas aquele "(8000)" pode trazer um uso desnecessário e pesado de memória, e até causar contenção entre as consultas sendo executadas.
Estes exemplos são legais porque nos cenários mostrados não há mensagens de erro, mas o uso exagerado de recursos devido a detalhes de modelagem podem afetar negativamente o uso de recursos e a performance do seu servidor.

A palestra será publicada pelo pessoal do PASS em http://www.sqlpass.org/Default.aspx?TabId=62

Espero que seja útil!

[]s,

GB

quarta-feira, 28 de novembro de 2012

SSMS 2012: Cadê o CTRL+R ?

No SQL Server 2012, o Management Studio (SSMS) é baseado na shell (a IDE "crua") do Visual Studio. A primeira coisa que notei foi que o atalho preferido de DBA's e desenvolvedores, o CTRL+R que serve pra mostrar e esconder o Result Pane, sumiu. Mas sem CTRL+R não dá. Simplesmente não dá.

Pra voltar ele pro ambiente:

  1. Vá em "Tools | Customize | Keyboard..."
  2. Selecione o comando "Window.ShowResultPane"
  3. Na lista "Use new shortcut in" selecione "SQL Query Editor"
  4. Coloque o cursor no campo "Press shortcut keys" e pressione "CTRL+R"
  5. Clique em "Assign" e "Ok".


Ufa. Alívio!

[]s,

GB

quinta-feira, 5 de julho de 2012

Recursos de Formação – TFS

Pra quem quer começar ou se especializar um pouco mais em TFS, aí vão alguns recursos gratuitos:

sexta-feira, 22 de junho de 2012

Como clonar VM's no Hyper-V

Biblioteca de VM's - ou biblioteca de VHD's - são excelente ferramentas para desenvolvedores criarem rapidamente um ambiente de testes. Mas você pode esbarrar em ferramentas instáveis e as famosas Blue Screen of Death se você não souber clonar corretamente os VHD's de sua biblioteca para a criação de novas VM's. A combinação de VHD's diferenciais com o sysprep reduz bastante o trabalho de subir corretamente clones de uma VM que precisam rodar em um domínio.

Contexto

Instalei um Windows 2008 R2 em uma VM do Hyper-V pra testes. Pra não ter que passar pelo tempo de instalação quando criasse novas VM's, separei o VHD dela, marquei como read-only, e comecei a criar novas VM's usando o recurso de discos diferenciais do Hyper-V, que permite criar discos baseado em um disco "pai". O disco "filho" - o disco diferencial - contém tudo o que o disco pai contém, e modificações nos arquivos são gravados no disco diferencial, deixando o disco pai intacto para servir de base para outros discos. (Se estiver curioso sobre discos diferenciais, dê uma olhada no artigo Virtual Hard Disk (VHD) Architecture explained).

Problema

Até o momento, eu tinha instalado tudo que eu queria testar em uma única VM. P.e., uma VM tinha SharePoint + SQL Server. Outra tinha um DC + Visual Studio + TFS + SQL Server. Eu testava tudo subindo só uma VM. Meu problema foi quando quis criar um domínio e subir várias VM's simultaneamente, usando o disco pai pra criar estas VM's.

O problema é que o Windows gera "coisas" durante a instalação que devem ser únicas em um domínio. Parece que existem várias destas "coisas", mas a que me deu problema foram os SID's das máquinas e dos usuários. Um SID é, basicamente, o identificador único de um objeto (máquina, usuário, grupo, etc) em um domínio. Se você simplesmente copia o VHD de uma VM e cria uma nova VM usando este VHD como disco, os SID's são duplicados. Isto causa problemas e erros quando estas VM's estão no mesmo domínio pois o Windows não espera encontrar SID's duplicados no domínio.

Solução

Como qualquer problema, tem várias formas de resolver isto. A que mais se adequou ao meu objetivo, que é criar uma biblioteca de VHD's para subir rapidamente ambientes Windows de desenvolvimento e testes que serão usados e depois descartados, foi:
  1. Criar uma VM e instalar o sistema operacional (Windows 2008 R2).
  2. Configurar opções comuns a todas as VM's que se basearão nesta instalação. Em particular: retirar a obrigatoriedade de troca de senha do administrador, customizar propriedades do Menu Iniciar, instalar o BgInfo para identificar facilmente as VM's.
  3. Executar o sysprep para colocar o Windows em um estado "acabei de ser instalado". O sysprep é uma ferramenta da Microsoft que prepara uma imagem de disco com uma instalação Windows para clonagem.
  4. Marcar o VHD como read-only.
  5. Criar novas VM's usando discos diferenciais baseados neste VHD "sysprep'd".
Para executar o passo 3 acima, após sua VM base estar completamente customizada, abra um command prompt, mude para C:\Windows\System32\Sysprep, e execute sysprep.exe. Selecione a opção "Enter System Out-of-Box Experience (OOB)", marque a opção "Generalize" (que faz com que o sysprep reinicialize tudo que tem que ser reinicializado - como os SID's), e selecione a opção "shutdown" para que a VM seja desligada após a execução do sysprep. Agora você tem um disco virtual com um Windows instalado e pronto para receber novos nome, SID, e as outras "coisas".


Quando você criar novas VM's que usam discos diferenciais baseados neste VHD, no primeiro boot será apresentado um "mini-setup" do Windows aonde ele pedirá somente algumas informações, gerará um nome para a nova máquina, e pronto - em menos de 2 minutos você tem uma nova VM limpinha pra trabalhar.

Observações

  1. O nome da nova máquina é auto-gerado, então geralmente é a primeira coisa que eu mudo.
  2. A máquina entra com IP auto-configurado ("APIPA" - 169.254.blablabla). Caso você esteja montando uma rede de VM's, tem que configurar o IP para ela entrar na rede.
  3. Dá dor de cabeça montar uma VM para fazer o sysprep se esta VM tem o SQL Server já instalado. Não é suportado até o SQL Server 2008. O que tenho feito é subir uma VM sysprep'd "limpa" - só o Windows -, colocar ela no domínio e instalar o SQL Server. Perco menos tempo assim do que ficar tentando solucionar problemas devido a fazer o syspred de uma VM já com SQL Server instalado. No SQL Server 2008 *R2* diz a MS que é suportado, mas nunca usei pra ver se dá muito trabalho ou não.
  4. Comecei a receber o erro "An attempt to resolve the DNS name of a domain controller in the domain being joined has failed" quando ia acrescentar uma nova VM no domínio - apesar de ter configurado o DC do domínio como o servidor DNS primário da nova VM, e conseguir pingar de uma máquina pra outra. Resumindo (muito), desabilitei o IPv6 em todas as máquinas e tudo voltou ao normal.
[]s,

GB

terça-feira, 8 de maio de 2012

Meu site SharePoint está com segurança integrada mas ainda aparece a janela de logon

Coloca ele como “Local Intranet” no IE que pára de aparecer.

(Forte concorrente a menor post do mundo se não fosse essa última frase)

quinta-feira, 3 de maio de 2012

SharePoint: Erro de deploy | Momento revolta :-P

Estava fazendo o deploy de uma solução no SharePoint e apareceu o erro "Error occurred in deployment step 'Activate Features': Operation is not valid due to the current state of the object.". Aí eu pensei "Hein?" e fui pro Google. E a primeira solução que encontrei foi:
1. Feche o VS2010
2. de um IISRESET no server
3. Abra o Task Manager e de um KILL em todos os processos w3wp.exe 
"Tu tá de sacanagem" eu pensei. Mas fiz isso e, sem mudar nada no código, o deploy passou a funcionar. Desenvolvimento no SharePoint é isso: faz sua estimativa, multiplica por 3 por causa dessas "peculiaridades", e reza pra ter alguém que já passou pela mesma coisa e postou na Internet. Se não chegasse tanta demanda nesse troço...

segunda-feira, 27 de fevereiro de 2012

Ambiente de Testes Virtualizado com Controlador de Domínio

Não sei direito porque, mas ouvi o pessoal de infra falando sobre problemas se serviços do seu domínio (tais como SQL e Exchange) sobem antes do controlador de domínio. Montei um domínio de testes para desenvolvimento no TFS e esbarrei nesse problema: serviços não subiam ou a conectividade entre eles não funcionava. Como as VM’s estão no Hyper-V, dá pra colocar um “atraso” no boot das máquinas, então configurei o DC sem o delay, e as outras máquinas com um delay de 30 segundos. Na tela de configuração de cada VM que deve subir com o delay:


Desta forma, o controlador de domínio sobe antes das outras VM’s, evitando esse tipo de problema.