¿Cuál es un método determinista para evaluar un tamaño razonable de agrupación de almacenamiento intermedio?

29

Estoy tratando de encontrar una forma sensata de entender si la max server memory (mb)configuración es adecuada (ya sea más baja o más alta, o permanecer como está). Soy consciente de que max server memory (mb)siempre debe ser lo suficientemente bajo como para dejar espacio para el sistema operativo, etc.

El entorno que estoy viendo tiene varios cientos de servidores; Necesito una fórmula confiable que pueda usar para determinar si el tamaño actual de la agrupación de almacenamientos intermedios es apropiado, ya que la RAM tiene un costo por GB asignado a cada servidor. Todo el entorno está virtualizado y la RAM "física" asignada a una VM se puede cambiar fácilmente hacia arriba o hacia abajo.

Tengo una instancia particular de SQL Server que estoy viendo ahora con un PLE de 1,100,052 segundos, lo que equivale a 12.7 días (la cantidad de tiempo que el servidor ha estado activo). El servidor tiene una configuración de memoria máxima del servidor de 2560 MB (2.5 GB), de los cuales solo 1380 MB (1.3 GB) están realmente comprometidos.

He leído varios artículos, incluido uno de Jonathan Keheyias ( publicación ) y otro de Paul Randal ( publicación ), y varios otros. Jonathan recomienda que el monitoreo de un PLE por debajo de 300 por 4 GB de agrupación de almacenamiento intermedio sea demasiado bajo. Para la instancia de SQL Server anterior, el 300 * (2.5 / 4) = 187resultado es un PLE de destino realmente muy bajo por debajo de 300. Esta instancia tiene 290 GB de datos de SQL Server (sin incluir archivos de registro), y solo se usa para pruebas de integración. Suponiendo que los últimos 12 días son representativos del uso típico de este servidor, diría que la max server memory (mb)configuración podría reducirse.

En el otro extremo de la escala, tengo otro servidor de prueba de integración con un PLE de 294, que tiene una max server memory (mb)configuración de solo 1 GB. Este servidor tiene solo 224 MB de datos de SQL Server sin incluir registros, y está ejecutando algunas bases de datos BizFlow. Este servidor podría beneficiarse de una max server memory (mb)configuración más alta .

Creo que un buen punto de partida para objetivos a los que se les puede asignar demasiada memoria podría incluir mirar:

SELECT 
    RamMB = physical_memory_in_bytes / 1048576
    , BufferPoolCommittedMB = bpool_committed * 8192E0 / 1048576
    , BufferPoolCommitTargetMB = bpool_commit_target * 8192E0 / 1048576
    , PercentOfDesiredSizeMB = CONVERT(INT,(CONVERT(DECIMAL(18,2),bpool_committed) 
                            / bpool_commit_target) * 100)
FROM sys.dm_os_sys_info;

Si BufferPoolCommitTargetMB / BufferPoolCommittedMBes mayor que 1, el servidor no está utilizando todo el grupo de búferes. Si la máquina en cuestión también tiene un PLE mayor que "x", entonces podría ser un buen candidato para una disminución en max server memory (mb).

Dado que el Buffer Manager:Lazy writes/seccontador de rendimiento rastrea el número de veces que SQLOS ha escrito páginas en el disco entre puntos de control debido a la presión de la memoria, esto podría ser otra buena cosa a tener en cuenta.

DECLARE @WaitTime DATETIME;
SET @WaitTime = '00:00:15';
DECLARE @NumSeconds INT;
SET @NumSeconds = DATEDIFF(SECOND, 0, @WaitTime);
DECLARE @LazyWrites1 BIGINT;
DECLARE @LazyWrites2 BIGINT;

SELECT @LazyWrites1 = cntr_value 
FROM sys.dm_os_performance_counters dopc
WHERE (
        dopc.counter_name LIKE 'Lazy writes/sec%' COLLATE SQL_Latin1_General_CP1_CI_AS
    )
    AND dopc.object_name = 'MSSQL$' + CONVERT(VARCHAR(255),
               SERVERPROPERTY('InstanceName')) + ':Buffer Manager';

WAITFOR DELAY @WaitTime;

SELECT @LazyWrites2 = cntr_value 
FROM sys.dm_os_performance_counters dopc
WHERE (
        dopc.counter_name LIKE 'Lazy writes/sec%' COLLATE SQL_Latin1_General_CP1_CI_AS
    )
    AND dopc.object_name = 'MSSQL$' + CONVERT(VARCHAR(255),
               SERVERPROPERTY('InstanceName')) + ':Buffer Manager';

SELECT LazyWritesPerSecond = (@LazyWrites2 - @LazyWrites1) / @NumSeconds;

El código anterior supone que el servidor está bajo carga durante los 15 segundos que tarda en ejecutarse; de ​​lo contrario, informará 0; que podría ser un falso negativo engañoso.

¿Debo también mirar las PAGELATCHIO_*estadísticas de espera o algún otro tipo de espera como un indicador de la presión de la memoria, o la falta de ella?

Mi pregunta es, ¿cómo puedo determinar de manera confiable un valor objetivo "bueno" para PLE y max server memory (mb)?

Max Vernon
fuente

Respuestas:

11

Como ya sabe, no existe una fórmula general para calcular la memoria máxima del servidor, puede hacer algunas matemáticas rápidas y alcanzar un valor, pero aún necesitaría la ayuda de los contadores de Perfmon para controlar el uso de la memoria y cambiar en consecuencia. Conozco la fórmula general a continuación y también la uso. Aprendí esta fórmula de este enlace

Para SQL Server 2005 a 2008 R2

Tenga en cuenta que desde SQL Server 2005 a 2008 R2 la memoria máxima del servidor solo controla la agrupación de almacenamiento intermedio. Entonces, la configuración máxima de memoria del servidor es un poco tediosa aquí e implica pocos cálculos

  1. Deje 2 G de memoria de inmediato para el sistema operativo Windows.

  2. Por supuesto, el sistema tendría antivirus en ejecución. Por favor, deje 1.5G para el antivirus. Tenga en cuenta que Mcafee y SQL Server no van de la mano, así que asegúrese de dejar suficiente para ello. También puede verificar el contador de perfmon Perfmon Process-> Private bytes and Working Setpara monitorear el uso de memoria de AV y otras aplicaciones pequeñas que se ejecutan en el cuadro de SQL Server

ingrese la descripción de la imagen aquí

  1. Tenga en cuenta los requisitos de memoria de los controladores / firmwares. Debe derivarlos según los requisitos de memoria de los controladores instalados en el sistema. La herramienta RAMMAP puede ayudar

  2. Considere los requisitos de memoria NonbPool (también conocido como MTL o MTR) de SQL Server.

    select  sum(multi_pages_kb)/1024 as multi_pages_mb from  sys.dm_os_memory_clerks

    + Max hilos de trabajo * 2MB

    + Memoria para asignaciones directas de Windows de aproximadamente 0 a 300 MB en la mayoría de los casos, pero es posible que tenga que aumentarla si hay muchos componentes de terceros cargados en el proceso de SQL Server (incluidos los dll de servidor vinculado, los dll de respaldo de terceros, etc.)

    + Si está utilizando CLR, agregue un poco de memoria adicional para CLR.

  3. Tenga en cuenta los requisitos de memoria de los trabajos (incluidos los agentes de replicación, el envío de registros, etc.) y los paquetes que se ejecutarán en el servidor. Puede variar de MB a GB según la cantidad de trabajos en ejecución. Para servidores de tamaño mediano, puede tomarlo como 250 MB

  4. Asegúrese de que haya suficiente espacio libre para el sistema operativo.

    Aproximadamente (100 MB por cada GB hasta 4G) + (50 MB por cada GB adicional hasta 12GB) + (25 MB por cada GB adicional hasta el tamaño de su RAM)

  5. Otros requisitos de memoria.

    Si tiene algún otro requisito de memoria específico para su entorno.

    Memoria máxima del servidor = Memoria física total - (1 + 2 + 3 + 4 + 5 + 6 + 7)

    No he incluido la configuración de memoria para SSIS.SSRS, SSAS también necesitaría restar la memoria requerida por estos servicios de la memoria física total del servidor.

    Después de haber configurado lo anterior, necesita monitorear los siguientes contadores

  • SQLServer: Buffer Manager - Page Life Expectancy (PLE)

  • SQLServer: Administrador de búfer - Puntos de control / seg .:

  • SQLServer: Administrador de memoria - Donaciones de memoria pendientes:

  • SQLServer: Administrador de memoria - Memoria del servidor de destino:

  • SQLServer: Administrador de memoria - Memoria total del servidor

Para SQL Server 2012/2014.

From SQL Server 2012 onwardsconfigurar la memoria máxima del servidor se ha vuelto fácil. Porque ahora la memoria máxima del servidor casi representa todo el consumo de memoria. La memoria máxima del servidor controla la asignación de memoria del servidor SQL, incluida la agrupación de almacenamiento intermedio, la memoria de compilación, todas las memorias caché, las concesiones de memoria qe, la memoria del administrador de bloqueos y la memoria CLR (básicamente cualquier "empleado" como se encuentra en dm_os_memory_clerks). La memoria para las pilas de subprocesos, los montones, los proveedores de servidores vinculados que no sean SQL Server o cualquier memoria asignada por una DLL "no SQL Server" no está controlada por la memoria máxima del servidor.

Puede asignar 75-80% a SQL Server y luego usar contadores de perfmon para monitorear el uso de memoria. En SQL Server 2012, algunos contadores de perfmon han quedado en desuso. El contador del administrador de búfer está en desuso, debe usar el contador del administrador de memoria

  • SQL Server: Administrador de memoria - Memoria del servidor de destino (KB)

  • SQL Server: Administrador de memoria - Memoria total del servidor (KB)

  • SQL Server: Administrador de memoria: memoria libre (KB)

  • SQL Server: Administrador de memoria - Memoria de caché de base de datos (KB)

Sobre el valor de PLE, he usado la fórmula de Joanthan y afortunadamente me ha funcionado.

Shanky
fuente
6

El desafío aquí es que los números no tienen en cuenta la experiencia del usuario final.

Gran ejemplo: tengo un servidor de base de datos utilizado para rastrear cada sitio web que visitan los empleados de la empresa. No me importa si no puede mantenerse al día con las inserciones durante las cargas máximas porque la aplicación front-end elimina las inserciones periódicamente, y las inserciones lentas no causan problemas a los usuarios. Los usuarios aún pueden navegar por la web sin ser detenidos por inserciones lentas.

En el momento SELECCIONAR, el departamento de recursos humanos simplemente dispara informes cuando se les solicita un historial de navegación sospechoso para un empleado determinado, pero no les importa cuánto tiempo demoran los informes, simplemente abren el informe y se van a hacer otras cosas.

El rendimiento debe comenzar con una pregunta: ¿los usuarios están contentos con el rendimiento? Si es así, deje el sistema donde está.

Brent Ozar
fuente
¿Incluso si está usando más memoria de la que necesita?
James Anderson
2
James: en general, no quiero hacer cambios que hagan que los usuarios se quejen. Si desea hacer eso, puede reducir gradualmente la cantidad de memoria para cada servidor hasta que los usuarios comiencen a quejarse, pero cuando ya estoy sobrecargado de trabajo, generalmente no tengo tiempo para dar esos pasos. Tengo que concentrarme en las tareas que harán felices a los usuarios infelices, en lugar de tratar de hacer infelices a los usuarios felices. ;-)
Brent Ozar
2
Buenos puntos, Brent. Me pidieron ver si algunos servidores están sobreaprovisionados porque pagamos por memoria por GB por año. Muchas de las instancias que estoy viendo tienen lo que considero que es una cantidad muy pequeña de RAM max server memory (mb), y como tal soy bastante reacio a reducir su tamaño. Sin embargo, algunas otras instancias tienen 1,000,000 + PLE, y como tales son candidatos potenciales bastante obvios para una caída de RAM. Claramente, la reducción de la RAM provocará un aumento de la OIA, y no estoy seguro de lo que el costo de que será.
Max Vernon
1
Además, mirar PLE en comparación con el max server memoryentorno es una especie de cosa de pollo y huevo; cuanto menor sea la max server memoryconfiguración, menor será el PLE mínimo "aceptable", por lo que podría quedar atrapado en una espiral cada vez más baja. Estoy seguro de que, como mencionas, el rendimiento del usuario se verá afectado en algún momento .
Max Vernon
Los contadores PLE son uno que debe evitar mirar desde 2012 en adelante o cuando tiene un sistema NUMA donde cada nodo se comporta como un pequeño asignador de memoria. Si lo desea, debe buscar PLE para cada nodo NUMA no completado, puede obtener un valor incorrecto
Shanky,
3

El T-SQL actual que estoy usando para evaluar PLE vs max server memoryes:

/*
    Purpose:            Returns a resultset describing various server level stats including PLE
                        Max and Min Server Memory, etc.
    By:                 Max Vernon
    Date:               2014-12-01
*/
SET NOCOUNT ON;

/*
    wait stats for PAGELATCH_IO
*/
DECLARE @Debug BIT;
SET @Debug = 0;
DECLARE @HTMLOutput BIT;
SET @HTMLOutput = 1;
DECLARE @WaitTime DATETIME;
SET @WaitTime = '00:00:15';
DECLARE @NumSeconds INT;
SET @NumSeconds = DATEDIFF(SECOND, 0, @WaitTime);
DECLARE @InstanceName NVARCHAR(255);
SET @InstanceName = CONVERT(NVARCHAR(255), SERVERPROPERTY('InstanceName'));
DECLARE @Version NVARCHAR(255);
DECLARE @VersionINT INT;
SET @Version = CONVERT(NVARCHAR(255),SERVERPROPERTY('ProductVersion'));
SET @VersionINT = CONVERT(INT, SUBSTRING(@Version,1 ,CHARINDEX('.',@Version)-1));
DECLARE @cmd NVARCHAR(MAX);
SET @cmd = '';
DECLARE @TaskCount INT;
DECLARE @TasksPerSecondAvg INT;
DECLARE @AvgWaitTimeInMSPerTask DECIMAL(10,2);
DECLARE @AvgWaitTimeInMSPerSecond DECIMAL(10,2);
DECLARE @TotalWaitTimeInMSOverall DECIMAL(10,2);
DECLARE @LazyWrites1 BIGINT;
DECLARE @LazyWrites2 BIGINT;
DECLARE @FreeListStallsSec1 BIGINT;
DECLARE @FreeListStallsSec2 BIGINT;
DECLARE @BatchReq1 BIGINT;
DECLARE @BatchReq2 BIGINT;
DECLARE @ws TABLE
(
    RunNum INT
    , wait_type SYSNAME
    , waiting_tasks_count BIGINT
    , wait_time_ms BIGINT
    , max_wait_time_ms BIGINT
    , signal_wait_time_ms BIGINT
);
INSERT INTO @ws
SELECT 1, dows.*
FROM sys.dm_os_wait_stats dows
WHERE dows.wait_type LIKE 'PAGEIOLATCH_%'
ORDER BY dows.waiting_tasks_count DESC;

SELECT @LazyWrites1 = cntr_value 
FROM sys.dm_os_performance_counters dopc
WHERE (
        dopc.counter_name LIKE N'Lazy writes/sec%' COLLATE SQL_Latin1_General_CP1_CI_AS
    )
    AND dopc.object_name = N'MSSQL$' + @InstanceName + N':Buffer Manager';

SELECT @FreeListStallsSec1 = cntr_value 
FROM sys.dm_os_performance_counters dopc
WHERE (
        dopc.counter_name LIKE N'Free list stalls/sec%' COLLATE SQL_Latin1_General_CP1_CI_AS
    )
    AND dopc.object_name = N'MSSQL$' + @InstanceName + N':Buffer Manager';

SELECT @BatchReq1 = cntr_value
FROM sys.dm_os_performance_counters dopc
WHERE dopc.counter_name LIKE N'Batch Requests/sec%' COLLATE SQL_Latin1_General_CP1_CI_AS
    AND dopc.object_name = N'MSSQL$' + @InstanceName + N':SQL Statistics';

WAITFOR DELAY @WaitTime;

INSERT INTO @ws
SELECT 2, dows.*
FROM sys.dm_os_wait_stats dows
WHERE dows.wait_type LIKE N'PAGEIOLATCH_%'
ORDER BY dows.waiting_tasks_count DESC;

SELECT @LazyWrites2 = cntr_value 
FROM sys.dm_os_performance_counters dopc
WHERE (
        dopc.counter_name LIKE N'Lazy writes/sec%' COLLATE SQL_Latin1_General_CP1_CI_AS
    )
    AND dopc.object_name = N'MSSQL$' + @InstanceName + N':Buffer Manager';

SELECT @FreeListStallsSec2 = cntr_value 
FROM sys.dm_os_performance_counters dopc
WHERE (
        dopc.counter_name LIKE N'Free list stalls/sec%' COLLATE SQL_Latin1_General_CP1_CI_AS
    )
    AND dopc.object_name = N'MSSQL$' + @InstanceName + N':Buffer Manager';

SELECT @TaskCount = SUM(w2.waiting_tasks_count - w1.waiting_tasks_count)
    , @TasksPerSecondAvg = CONVERT(DECIMAL(10,2), (SUM(w2.waiting_tasks_count) - SUM(w1.waiting_tasks_count))) / @NumSeconds
    , @AvgWaitTimeInMSPerTask = CONVERT(DECIMAL(10,2),(SUM(w2.wait_time_ms) - SUM(w1.wait_time_ms))) / CONVERT(DECIMAL(10,2),(SUM(w2.waiting_tasks_count) - SUM(w1.waiting_tasks_count)))
    , @AvgWaitTimeInMSPerSecond = (CONVERT(DECIMAL(10,2), (SUM(w2.waiting_tasks_count) - SUM(w1.waiting_tasks_count))) / @NumSeconds) * (CONVERT(DECIMAL(10,2),(SUM(w2.wait_time_ms) - SUM(w1.wait_time_ms))) / CONVERT(DECIMAL(10,2),(SUM(w2.waiting_tasks_count) - SUM(w1.waiting_tasks_count))))
    , @TotalWaitTimeInMSOverall = SUM(w2.wait_time_ms) - SUM(w1.wait_time_ms)
FROM (SELECT * FROM @ws ws1 WHERE ws1.RunNum = 1) w1
    INNER JOIN (SELECT * FROM @ws ws2 WHERE ws2.RunNum = 2) w2 ON w1.wait_type = w2.wait_type
WHERE (w2.waiting_tasks_count - w1.waiting_tasks_count) > 0;

SELECT @BatchReq2 = cntr_value
FROM sys.dm_os_performance_counters dopc
WHERE dopc.counter_name LIKE N'Batch Requests/sec%' COLLATE SQL_Latin1_General_CP1_CI_AS
    AND dopc.object_name = N'MSSQL$' + @InstanceName + N':SQL Statistics';

/*
    configured values for max server memory and min server memory, etc
*/
DECLARE @MaxServerMemory BIGINT;
DECLARE @MaxServerMemoryPages BIGINT;
DECLARE @MinServerMemory BIGINT;
DECLARE @MinPLE BIGINT;
DECLARE @RamMB BIGINT;
DECLARE @BufferPoolCommittedMB BIGINT;
DECLARE @BufferPoolCommitTargetMB BIGINT;
DECLARE @PercentOfDesiredSizeMB INT;
DECLARE @TargetPageLifeExpectancyPer4GB BIGINT;
SET @TargetPageLifeExpectancyPer4GB = 60 * 120; /* 120 minutes */
/*DECLARE @VMType VARCHAR(255);*/
DECLARE @PLESeconds BIGINT;

SELECT @MaxServerMemory = CONVERT(BIGINT,c.value)
FROM sys.configurations c
WHERE c.name = N'max server memory (mb)'

SET @MaxServerMemoryPages = @MaxServerMemory / 128; /* 8KB pages */

SELECT @MinServerMemory = CONVERT(BIGINT,c.value)
FROM sys.configurations c
WHERE c.name = N'min server memory (mb)'

SET @MinPLE = @MaxServerMemory / 4096E0 * @TargetPageLifeExpectancyPer4GB;

IF @VersionINT < 11
BEGIN
    SET @cmd = 'SELECT 
    @RamMB = dosi.physical_memory_in_bytes / 1048576
    , @BufferPoolCommittedMB = dosi.bpool_committed * 8192E0 / 1048576
    , @BufferPoolCommitTargetMB = dosi.bpool_commit_target * 8192E0 / 1048576
    , @PercentOfDesiredSizeMB = CONVERT(INT,(CONVERT(DECIMAL(18,2),dosi.bpool_committed) / dosi.bpool_commit_target) * 100)
FROM sys.dm_os_sys_info dosi;
';
END
ELSE 
BEGIN 
SET @cmd = 'SELECT 
    @RamMB = dosi.physical_memory_kb / 1024
    , @BufferPoolCommittedMB = dosi.committed_kb / 1024
    , @BufferPoolCommitTargetMB = dosi.committed_target_kb / 1024
    , @PercentOfDesiredSizeMB = CONVERT(INT,(CONVERT(DECIMAL(18,2),dosi.committed_kb) / dosi.committed_target_kb) * 100)
FROM sys.dm_os_sys_info dosi;';
END
EXEC sp_executesql @cmd
    , N'@RamMB BIGINT OUTPUT, @BufferPoolCommittedMB BIGINT OUTPUT, @BufferPoolCommitTargetMB BIGINT OUTPUT, @PercentOfDesiredSizeMB INT OUTPUT' 
    , @RamMB = @RamMB OUT
    , @BufferPoolCommittedMB = @BufferPoolCommittedMB OUT
    , @BufferPoolCommitTargetMB = @BufferPoolCommitTargetMB OUT
    , @PercentOfDesiredSizeMB = @PercentOfDesiredSizeMB OUT;

/*
    Page Life Expectancy for all memory nodes
*/
SELECT @PLESeconds = CONVERT(BIGINT, cntr_value) 
FROM sys.dm_os_performance_counters dopc
WHERE dopc.counter_name LIKE N'Page Life Expectancy%' COLLATE SQL_Latin1_General_CP1_CI_AS
    AND dopc.object_name = N'MSSQL$' + @InstanceName + N':Buffer Manager';

/*
    Total data in all user-databases.
*/
DECLARE @TotalDBSpaceUsed TABLE
(
    TotalSpaceUsedInMB BIGINT
);
DECLARE @SpaceUsedInMB BIGINT;
SET @cmd = '';
SELECT @cmd = @cmd + CASE WHEN @cmd = '' THEN '' ELSE '
UNION ALL
' END + 
'
SELECT DatabaseName = ''' + d.name + ''' 
    , AllocType = au.type_desc
    , TotalPagesInMB = SUM(au.total_pages) * 8192E0 / 1048576
FROM ' + QUOTENAME(d.name) + '.sys.allocation_units au
WHERE au.type > 0
GROUP BY au.type_desc
'
FROM master.sys.databases d
WHERE d.database_id > 4;
SET @cmd = 'SELECT SUM(TotalPagesInMB)
FROM (
' + @cmd + '
) t;'; 
INSERT INTO @TotalDBSpaceUsed (TotalSpaceUsedInMB)
EXEC sp_executesql @cmd;
SELECT @SpaceUsedInMB = TDSU.TotalSpaceUsedInMB
FROM @TotalDBSpaceUsed TDSU;

IF @Debug = 1
BEGIN
    SELECT ServerName = @@SERVERNAME
        , InstanceName = @InstanceName
        , DatabaseSpaceUsedMB = @SpaceUsedInMB
        , PLEinSeconds = @PLESeconds
        , MinAcceptablePLE = @MinPLE
        , MinServerMemoryMB = @MinServerMemory
        , MaxServerMemoryMB = @MaxServerMemory
        , TotalServerRAMinMB = @RamMB
        , BufferPoolCommittedMB = @BufferPoolCommittedMB
        , BufferPoolCommitTargetMB = @BufferPoolCommitTargetMB
        , PercentBufferPoolCommitted = @PercentOfDesiredSizeMB
        , BatchReqPerSecond = (@BatchReq2 - @BatchReq1) / @NumSeconds
        , LazyWritesPerSecond = (@LazyWrites2 - @LazyWrites1) / @NumSeconds
        , FreeListStallsPerSecond = (@FreeListStallsSec2 - @FreeListStallsSec2) / @NumSeconds
        /*, VMType = @VMType*/
        , IOTaskCount = @TaskCount 
        , TaskPerSecondAvg = @TasksPerSecondAvg 
        , AvgWaitTimeInMSPerTask = @AvgWaitTimeInMSPerTask 
        , AvgWaitTimeInMSPerSecond = @AvgWaitTimeInMSPerSecond 
        , TotalWaitTimeInMSOverall  = @TotalWaitTimeInMSOverall
        , SamplePeriodinSec = @NumSeconds;

    SELECT MaxServerMemorySuggested = 
            CASE WHEN @BufferPoolCommittedMB < @BufferPoolCommitTargetMB 
            THEN @BufferPoolCommittedMB 
            ELSE ((CONVERT(DECIMAL(18,4), @MinPLE) / @PLESeconds) * @MaxServerMemory) 
                    + (((@LazyWrites2 - @LazyWrites1) / @NumSeconds) * 64) 
                    + ((@FreeListStallsSec2 - @FreeListStallsSec2) / @NumSeconds) * 64 
            END
        , Reason = CASE WHEN @BufferPoolCommittedMB < @BufferPoolCommitTargetMB THEN N'Committed MB less than current Max Server Memory'
            ELSE N'Calculated based on PLE, Lazy Writes / second and List Stalls / second' END
        , LazyWritesX64 = (((@LazyWrites2 - @LazyWrites1) / @NumSeconds) * 64)
        , ListStallsX64 = ((@FreeListStallsSec2 - @FreeListStallsSec2) / @NumSeconds) * 64;
END

DECLARE @Out TABLE
(
    KeyID INT IDENTITY(1,1)
    , ItemDesc NVARCHAR(255)
    , ItemValue SQL_VARIANT
    , IsDebug BIT DEFAULT(0)
);

INSERT INTO @Out (ItemDesc, ItemValue, IsDebug)
VALUES (N'Server Name', CONVERT(NVARCHAR(255),@@SERVERNAME), 1);

INSERT INTO @Out (ItemDesc, ItemValue)
VALUES (N'Data Space Used (MB)', @SpaceUsedInMB);

INSERT INTO @Out (ItemDesc, ItemValue)
VALUES (N'Page Life Expectancy (sec)', @PLESeconds);

INSERT INTO @Out (ItemDesc, ItemValue)
VALUES (N'Minimum Acceptable Page Life Expectancy (sec)', @MinPLE);

INSERT INTO @Out (ItemDesc, ItemValue)
VALUES (N'Minimum Server Memory (MB)', @MinServerMemory);

INSERT INTO @Out (ItemDesc, ItemValue)
VALUES (N'Maximum Server Memory (MB)', @MaxServerMemory);

INSERT INTO @Out (ItemDesc, ItemValue)
VALUES (N'Total Server RAM in MB', @RamMB);

INSERT INTO @Out (ItemDesc, ItemValue)
VALUES (N'Buffer Pool Committed MB', @BufferPoolCommittedMB);

INSERT INTO @Out (ItemDesc, ItemValue)
VALUES (N'Buffer Pool Commit Target MB', @BufferPoolCommitTargetMB);

INSERT INTO @Out (ItemDesc, ItemValue)
VALUES (N'Percent of Buffer Pool Committed', @PercentOfDesiredSizeMB);

INSERT INTO @Out (ItemDesc, ItemValue)
VALUES (N'Batch Requests Per Second', (@BatchReq2 - @BatchReq1) / @NumSeconds);

INSERT INTO @Out (ItemDesc, ItemValue)
VALUES (N'Lazy Writes Per Second', (@LazyWrites2 - @LazyWrites1) / @NumSeconds);

INSERT INTO @Out (ItemDesc, ItemValue)
VALUES (N'Free List Stalls Per Second', (@FreeListStallsSec2 - @FreeListStallsSec2) / @NumSeconds);

INSERT INTO @Out (ItemDesc, ItemValue)
VALUES (N'IO Task Count', @TaskCount);

INSERT INTO @Out (ItemDesc, ItemValue)
VALUES (N'Task Per Second Avg', @TasksPerSecondAvg);

INSERT INTO @Out (ItemDesc, ItemValue)
VALUES (N'Avg Wait Time In MS Per Task', @AvgWaitTimeInMSPerTask);

INSERT INTO @Out (ItemDesc, ItemValue)
VALUES (N'Avg Wait Time In MS Per Second', @AvgWaitTimeInMSPerSecond);

INSERT INTO @Out (ItemDesc, ItemValue)
VALUES (N'Total Wait Time In MS Overall', @TotalWaitTimeInMSOverall);

INSERT INTO @Out (ItemDesc, ItemValue)
VALUES (N'Sample Period in Seconds', @NumSeconds);

INSERT INTO @Out (ItemDesc, ItemValue)
VALUES (N'Lazy Writes per Second', ((@LazyWrites2 - @LazyWrites1) / @NumSeconds));

INSERT INTO @Out (ItemDesc, ItemValue)
VALUES (N'List Stalls per Second', ((@FreeListStallsSec2 - @FreeListStallsSec2) / @NumSeconds));

INSERT INTO @Out (ItemDesc, ItemValue)
VALUES (N'Recommended Max Memory (MB)', N'');

INSERT INTO @Out (ItemDesc, ItemValue)
VALUES (N'Recommended Max Memory Reason', N'');

INSERT INTO @Out (ItemDesc, ItemValue, IsDebug)
VALUES (N'Recommended Max Memory Signal', 0, 1);

/*
    Add memory if Lazy Writes occurred
    Add 64MB per Lazy Write (just for fun)
*/
DECLARE @LazyWritesMB INT;
SET @LazyWritesMB = (((@LazyWrites2 - @LazyWrites1) / @NumSeconds) * 64);

/*
    Add memory if Free List Stalls occurred
    Add 128MB per Free List Stall
*/
DECLARE @FreeListStallMB INT;
SET @FreeListStallMB = (((@FreeListStallsSec2 - @FreeListStallsSec2) / @NumSeconds) * 128);

/*
    Add the Additional memory requirements to the Recommended Max Memory row
*/
DECLARE @AdditionalMemory INT;
SET @AdditionalMemory = 
    @LazyWritesMB
    + @FreeListStallMB;

IF (@MaxServerMemory + @AdditionalMemory < 1024) AND (@PLESeconds >= @MinPLE)
BEGIN
    UPDATE @Out 
    SET ItemValue = @MaxServerMemory
    WHERE ItemDesc = N'Recommended Max Memory (MB)';

    UPDATE @Out 
    SET ItemValue = 'Max Server Memory is low, however PLE is acceptable'
    WHERE ItemDesc = N'Recommended Max Memory Reason';

    UPDATE @Out 
    SET ItemValue = 1
    WHERE ItemDesc = N'Recommended Max Memory Signal';
END

IF ((@BufferPoolCommittedMB + @AdditionalMemory) < @BufferPoolCommitTargetMB) AND (@PLESeconds >= @MinPLE)
BEGIN
    UPDATE @Out 
    SET ItemValue = @BufferPoolCommittedMB + @AdditionalMemory
    WHERE ItemDesc = N'Recommended Max Memory (MB)';

    UPDATE @Out 
    SET ItemValue = 'Buffer pool committed is less than Max Server Memory, and PLE is acceptable.'
    WHERE ItemDesc = N'Recommended Max Memory Reason';

    UPDATE @Out 
    SET ItemValue = 2
    WHERE ItemDesc = N'Recommended Max Memory Signal';
END

DECLARE @PLEMultiplier DECIMAL(10,2);
SET @PLEMultiplier = (CONVERT(DECIMAL(10,2),@MinPLE) / CONVERT(DECIMAL(10,2), @PLESeconds));
IF @PLEMultiplier < 0.90 SET @PLEMultiplier = 0.90;
IF @PLEMultiplier > 1.10 SET @PLEMultiplier = 1.10;

INSERT INTO @Out (ItemDesc, ItemValue, IsDebug)
VALUES (N'PLE Multiplier', @PLEMultiplier, 1);

IF /*(@MaxServerMemory + @AdditionalMemory >= 1024) AND*/ (@PLESeconds <= @MinPLE)
BEGIN
    UPDATE @Out 
    SET ItemValue = 
        (SELECT TOP(1) Inc
        FROM (
            SELECT Inc = t.RowNum * 256
            FROM (
                SELECT RowNum = CONVERT(BIGINT,ROW_NUMBER() OVER (ORDER BY o.object_id))
                FROM sys.objects o, sys.objects o1
                ) t
            WHERE (t.RowNum * 256) <  CONVERT(BIGINT,POWER(2,30))
            ) t1
        WHERE t1.Inc > CONVERT(INT, (@MaxServerMemory * @PLEMultiplier))
        ORDER BY t1.Inc)
    WHERE ItemDesc = N'Recommended Max Memory (MB)';

    UPDATE @Out 
    SET ItemValue = 'Low PLE indicates Max Server Memory should be adjusted upwards.'
    WHERE ItemDesc = N'Recommended Max Memory Reason';

    UPDATE @Out 
    SET ItemValue = 3
    WHERE ItemDesc = N'Recommended Max Memory Signal';
END

IF (@MaxServerMemory + @AdditionalMemory >= 1024) AND (@PLESeconds > @MinPLE)
BEGIN
    UPDATE @Out 
    SET ItemValue = 
        (SELECT TOP(1) Inc
        FROM (
            SELECT Inc = t.RowNum * 256
            FROM (
                SELECT RowNum = CONVERT(BIGINT,ROW_NUMBER() OVER (ORDER BY o.object_id))
                FROM sys.objects o, sys.objects o1
                ) t
            WHERE (t.RowNum * 256) <  CONVERT(BIGINT,POWER(2,30))
            ) t1
        WHERE t1.Inc <= CONVERT(INT, (@MaxServerMemory * @PLEMultiplier))
        ORDER BY t1.Inc DESC)
    WHERE ItemDesc = N'Recommended Max Memory (MB)';

    UPDATE @Out 
    SET ItemValue = 'High PLE indicates Max Server Memory could be adjusted downwards.'
    WHERE ItemDesc = N'Recommended Max Memory Reason';

    UPDATE @Out 
    SET ItemValue = 4
    WHERE ItemDesc = N'Recommended Max Memory Signal';
END

DECLARE @RecommendedMaxServerMemory INT;
SELECT  @RecommendedMaxServerMemory = CONVERT(INT,ItemValue)
FROM @Out o 
WHERE o.ItemDesc = N'Recommended Max Memory (MB)';

IF @RecommendedMaxServerMemory > (@MaxServerMemory * 0.96)
    AND @RecommendedMaxServerMemory < (@MaxServerMemory * 1.04)
BEGIN
    UPDATE @Out
    SET ItemValue = @MaxServerMemory
    WHERE ItemDesc = N'Recommended Max Memory (MB)';
    UPDATE @Out
    SET ItemValue = 'No changed recommended'
    WHERE ItemDesc = N'Recommended Max Memory Reason';
    UPDATE @Out 
    SET ItemValue = 0
    WHERE ItemDesc = N'Recommended Max Memory Signal';
END 

IF (@HTMLOutput = 1)
BEGIN
    SELECT ItemValue
        , HTMLOutput = '<table>' + 
            (
                SELECT 'td' = ItemDesc
                    , ''
                    , 'td' = ItemValue
                    , ''
                FROM @Out o
                WHERE CASE WHEN @Debug = 0 THEN o.IsDebug ELSE 0 END = 0
                ORDER BY o.KeyID
                FOR XML PATH('tr')
            ) +
            '</table>'
    FROM @Out o
    WHERE o.ItemDesc = N'Recommended Max Memory Signal';
END
ELSE
BEGIN
    SELECT *
    FROM @Out o
    WHERE CASE WHEN @Debug = 0 THEN o.IsDebug ELSE 0 END = 0
    ORDER BY o.KeyID;
END

Este código compara el PLE con un PLE "aceptable" mínimo para la cantidad de max server memorysistema que ha configurado. Si el PLE es apreciablemente más alto que el número aceptable, sugiere un máximo de 10% más bajo max server memory. Si el PLE es más bajo que el PLE aceptable, sugiere un máximo de 10% más max server memory.

Si la cantidad real de agrupación de almacenamiento intermedio comprometida es menor que el tamaño de la agrupación de almacenamiento intermedio de destino, sugiere reducir max server memorya esa cantidad, más algo de memoria adicional para subprocesos, escrituras diferidas, etc.

El código también analiza varios contadores de rendimiento para cosas como Lazy Writes / second, Free List Stalls y Batch Requests.

El código no es perfecto, lo comparto aquí para obtener información y para el beneficio de los futuros usuarios de SO.

Max Vernon
fuente
1
Ten en cuenta que a partir del destino del grupo de búferes de SQL Server 2012 y comprometido no tiene sentido y estos contadores son obsoletos. En su lugar, debe usar Memory Manager Target Committed (KB) y current committed. Si desea leer más por qué da un valor incorrecto social.technet.microsoft.com/wiki/contents/articles/…
Shanky