Uso de RAM de Metarquivo do Windows Server 2008 R2

33

Eu tenho um servidor que executa o Windows Server 2008 R2 x64 com 4 GB de RAM, que hospeda cerca de 2 a 3 milhões de arquivos, a maioria dos quais são arquivos de imagem.

Durante uma semana, observei que os aplicativos no servidor estavam diminuindo o ritmo devido à paginação excessiva no disco devido à pouca memória, o que tem um efeito indireto em todos os serviços em execução no momento, causando um grande problema de desempenho.

Após a investigação no Gerenciador de Tarefas, notei que quase todos os 4GB estavam em uso, mas quando você olha na guia Processos, a soma de todo o uso de memória não se soma e, no máximo, apenas 1,5 GB é supostamente em uso.

Usando o Google para encontrar uma solução, parece que a maior parte da RAM foi usada no "Metafile", que é um cache de informações NTFS para arquivos no sistema de arquivos, para que o sistema não precise consultar a MFT para obter informações. novamente. Esse cache nunca é limpo ou marcado como "cache" no Gerenciador de Tarefas ou como "Standby" no RamMap da Sysinternal.

Houve uma sugestão para instale o hotfix KB979149, mas ao tentar instalá-lo, ele diz "Esta atualização não é aplicável ao seu computador".

As únicas correções temporárias que encontrei até agora são:

  1. Use o RAMmap da Sysinternals para "Empty System Working Set" a cada 1-3 dias, o que marca o cache como "standby" e "cache" no Gerenciador de Tarefas para que a RAM possa ser usada por outros aplicativos.
  2. Reinicialize a máquina, o que é indesejável, pois esse servidor está veiculando sites públicos.

No momento, estou tendo que executar o 2. corrigir todos os dias para evitar que ele atinja os níveis de gargalo.

Antes: (800MB RAM usada - outros aplicativos não podem usar essa RAM)

Depois:(800MBRAMmarcadacomocache-disponívelparaoutrosaplicativos)

Portanto, minha pergunta para todos vocês é: Existe algum método disponível para limitar o uso de RAM desse metarquivo?

    
por al2k4 27.10.2011 / 12:19

7 respostas

16

O melhor método para lidar com esse problema é usar o SetSystemFileCacheSize API como MS KB976618 instrui usado para instruir .

Não limpe periodicamente o cache

Usar a função SetSystemFileCacheSize em vez de limpar o cache periodicamente melhora o desempenho e a estabilidade. Limpar o cache periodicamente resultará em muito metarquivo e outras informações sendo removidas da memória e O Windows terá que reler as informações necessárias de volta para a RAM a partir do HDD. Isso cria uma queda repentina e grave no desempenho por vários segundos sempre que você limpa o cache, seguido por um bom desempenho que se degrada lentamente à medida que a memória é preenchida com dados de metarquivo.

Usar a função SetSystemFileCacheSize define o mínimo e o máximo que resultará no Windows sinalizando dados de metarquivo antigos em excesso como memória de espera que as funções normais de armazenamento em cache podem usar ou descartar de acordo com as demandas de recursos atuais e prioridades de cache normais. Isso também permite mais dados de metarquivo do que o máximo de memória ativa que você definiu, para estar na memória como dados de espera, se o Windows não estiver usando a memória para qualquer outra coisa, enquanto mantém muita memória disponível. Esta é a situação ideal, mantendo as características de desempenho do sistema boas o tempo todo.

Programas de terceiros não são suportados pelo MS

Se você é como eu e não quer executar um binário de algum terceiro desconhecido em seus servidores de produção, você quer uma ferramenta oficial do MS ou algum código que possa inspecionar antes de executar nesses servidores. A ferramenta DynCache para 2008 R2 é praticamente impossível de obter a partir de M $ sem pagar por um caso de suporte e, francamente, com base no código de 2008, parece excessivamente inchado para a tarefa, pois o Windows já possui a lógica interna necessária para dimensionar dinamicamente o cache - só precisa saber um máximo apropriado para o seu sistema.

Solução para todos os itens acima

Eu escrevi um script Powershell que funciona em máquinas de 64 bits. Você precisa executá-lo como administrador com privilégios elevados. Você deve ser capaz de executá-lo, como é, em qualquer windows x64 Vista / Server 2008 até e incluindo 10 / Server 2012 R2 com qualquer quantidade de RAM. Você não precisa instalar nenhum software adicional e, como resultado, manter seu servidor / estação de trabalho totalmente suportado pelo MS.

Você deve executar este script em cada inicialização com privilégios elevados para que a configuração seja permanente. O Agendador de Tarefas do Windows pode fazer isso por você. Se a instalação do Windows estiver dentro de uma máquina virtual e você alterar a quantidade de RAM alocada para essa VM, também deverá executá-la após a alteração.

Você pode executar esse script a qualquer momento em um sistema em execução, mesmo durante o uso de produção, sem ter que reinicializar o sistema ou encerrar nenhum serviço.

# Filename: setfc.ps1
$version = 1.1

#########################
# Settings
#########################

# The percentage of physical ram that will be used for SetSystemFileCache Maximum
$MaxPercent = 12.5

#########################
# Init multipliers
#########################
$OSBits = ([System.IntPtr]::Size) * 8
switch ( $OSBits)
{
    32 { $KiB = [int]1024 }
    64 { $KiB = [long]1024 }
    default {
        # not 32 or 64 bit OS. what are you doing??
        $KiB = 1024 # and hope it works anyway
        write-output "You have a weird OS which is $OSBits bit. Having a go anyway."
    }
}
# These values "inherit" the data type from $KiB
$MiB = 1024 * $KiB
$GiB = 1024 * $MiB
$TiB = 1024 * $GiB
$PiB = 1024 * $TiB
$EiB = 1024 * $PiB


#########################
# Calculated Settings
#########################

# Note that because we are using signed integers instead of unsigned
# these values are "limited" to 2 GiB or 8 EiB for 32/64 bit OSes respectively

$PhysicalRam = 0
$PhysicalRam = [long](invoke-expression (((get-wmiobject -class "win32_physicalmemory").Capacity) -join '+'))
if ( -not $? ) {
    write-output "Trying another method of detecting amount of installed RAM."
 }
if ($PhysicalRam -eq 0) {
    $PhysicalRam = [long]((Get-WmiObject -Class Win32_ComputerSystem).TotalPhysicalMemory) # gives value a bit less than actual
}
if ($PhysicalRam -eq 0) {
    write-error "Cannot Detect Physical Ram Installed. Assuming 4 GiB."
    $PhysicalRam = 4 * $GiB
}
$NewMax = [long]($PhysicalRam * 0.01 * $MaxPercent)
# The default value
# $NewMax = 1 * $TiB


#########################
# constants
#########################

# Flags bits
$FILE_CACHE_MAX_HARD_ENABLE     = 1
$FILE_CACHE_MAX_HARD_DISABLE    = 2
$FILE_CACHE_MIN_HARD_ENABLE     = 4
$FILE_CACHE_MIN_HARD_DISABLE    = 8


################################
# C# code
# for interface to kernel32.dll
################################
$source = @"
using System;
using System.Runtime.InteropServices;

namespace MyTools
{
    public static class cache
    {
        [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern bool GetSystemFileCacheSize(
            ref IntPtr lpMinimumFileCacheSize,
            ref IntPtr lpMaximumFileCacheSize,
            ref IntPtr lpFlags
            );

        [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern bool SetSystemFileCacheSize(
          IntPtr MinimumFileCacheSize,
          IntPtr MaximumFileCacheSize,
          Int32 Flags
        );

        [DllImport("kernel32", CharSet = CharSet.Unicode)]
        public static extern int GetLastError();

        public static bool Get( ref IntPtr a, ref IntPtr c, ref IntPtr d )
        {
            IntPtr lpMinimumFileCacheSize = IntPtr.Zero;
            IntPtr lpMaximumFileCacheSize = IntPtr.Zero;
            IntPtr lpFlags = IntPtr.Zero;

            bool b = GetSystemFileCacheSize(ref lpMinimumFileCacheSize, ref lpMaximumFileCacheSize, ref lpFlags);

            a = lpMinimumFileCacheSize;
            c = lpMaximumFileCacheSize;
            d = lpFlags;
            return b;
        }


        public static bool Set( IntPtr MinimumFileCacheSize, IntPtr MaximumFileCacheSize, Int32 Flags )
        {
            bool b = SetSystemFileCacheSize( MinimumFileCacheSize, MaximumFileCacheSize, Flags );
            if ( !b ) {
                Console.Write("SetSystemFileCacheSize returned Error with GetLastError = ");
                Console.WriteLine( GetLastError() );
            }
            return b;
        }
    }

    public class AdjPriv
    {
        [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
        internal static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall, ref TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen);

        [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
        internal static extern bool OpenProcessToken(IntPtr h, int acc, ref IntPtr phtok);

        [DllImport("advapi32.dll", SetLastError = true)]
        internal static extern bool LookupPrivilegeValue(string host, string name, ref long pluid);

        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        internal struct TokPriv1Luid
        {
            public int Count;
            public long Luid;
            public int Attr;
        }
        internal const int SE_PRIVILEGE_ENABLED = 0x00000002;
        internal const int SE_PRIVILEGE_DISABLED = 0x00000000;
        internal const int TOKEN_QUERY = 0x00000008;
        internal const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;

        public static bool EnablePrivilege(long processHandle, string privilege, bool disable)
        {
            bool retVal;
            TokPriv1Luid tp;
            IntPtr hproc = new IntPtr(processHandle);
            IntPtr htok = IntPtr.Zero;
            retVal = OpenProcessToken(hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok);
            tp.Count = 1;
            tp.Luid = 0;
            if(disable)
            {
                tp.Attr = SE_PRIVILEGE_DISABLED;
            } else {
                tp.Attr = SE_PRIVILEGE_ENABLED;
            }
            retVal = LookupPrivilegeValue(null, privilege, ref tp.Luid);
            retVal = AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero);
            return retVal;
        }
    }
}
"@
# Add the c# code to the powershell type definitions
Add-Type -TypeDefinition $source -Language CSharp

#########################
# Powershell Functions
#########################
function output-flags ($flags)
{
    Write-output ("FILE_CACHE_MAX_HARD_ENABLE  : " + (($flags -band $FILE_CACHE_MAX_HARD_ENABLE) -gt 0) )
    Write-output ("FILE_CACHE_MAX_HARD_DISABLE : " + (($flags -band $FILE_CACHE_MAX_HARD_DISABLE) -gt 0) )
    Write-output ("FILE_CACHE_MIN_HARD_ENABLE  : " + (($flags -band $FILE_CACHE_MIN_HARD_ENABLE) -gt 0) )
    Write-output ("FILE_CACHE_MIN_HARD_DISABLE : " + (($flags -band $FILE_CACHE_MIN_HARD_DISABLE) -gt 0) )
    write-output ""
}

#########################
# Main program
#########################

write-output ""

#########################
# Get and set privilege info
$ProcessId = $pid
$processHandle = (Get-Process -id $ProcessId).Handle
$Privilege = "SeIncreaseQuotaPrivilege"
$Disable = $false
Write-output ("Enabling SE_INCREASE_QUOTA_NAME status: " + [MyTools.AdjPriv]::EnablePrivilege($processHandle, $Privilege, $Disable) )

write-output ("Program has elevated privledges: " + ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") )
write-output ""
whoami /PRIV | findstr /I "SeIncreaseQuotaPrivilege" | findstr /I "Enabled"
if ( -not $? )  {
    write-error "user Security Token SE_INCREASE_QUOTA_NAME: Disabled'r'n"
}
write-output "'r'n"


#########################
# Get Current Settings
# Init variables
$SFCMin = 0
$SFCMax = 0
$SFCFlags = 0
#Get Current values from kernel
$status = [MyTools.cache]::Get( [ref]$SFCMin, [ref]$SFCMax, [ref]$SFCFlags )
#typecast values so we can do some math with them
$SFCMin = [long]$SFCMin
$SFCMax = [long]$SFCMax
$SFCFlags = [long]$SFCFlags
write-output "Return values from GetSystemFileCacheSize are: "
write-output "Function Result : $status"
write-output "            Min : $SFCMin"
write-output ("            Max : $SFCMax ( " + $SFCMax / 1024 / 1024 / 1024 + " GiB )")
write-output "          Flags : $SFCFlags"
output-flags $SFCFlags


#########################
# Output our intentions
write-output ("Physical Memory Detected : $PhysicalRam ( " + $PhysicalRam / $GiB + " GiB )")
write-output ("Setting Max to " + $MaxPercent + "% : $NewMax ( " + $NewMax / $MiB + " MiB )'r'n")

#########################
# Set new settings
$SFCFlags = $SFCFlags -bor $FILE_CACHE_MAX_HARD_ENABLE # set max enabled
$SFCFlags = $SFCFlags -band (-bnot $FILE_CACHE_MAX_HARD_DISABLE) # unset max dissabled if set
# or if you want to override this calculated value
# $SFCFlags = 0
$status = [MyTools.cache]::Set( $SFCMin, $NewMax, $SFCFlags ) # calls the c# routine that makes the kernel API call
write-output "Set function returned: $status'r'n"
# if it was successfull the new SystemFileCache maximum will be NewMax
if ( $status ) {
    $SFCMax = $NewMax
}


#########################
# After setting the new values, get them back from the system to confirm
# Re-Init variables
$SFCMin = 0
$SFCMax = 0
$SFCFlags = 0
#Get Current values from kernel
$status = [MyTools.cache]::Get( [ref]$SFCMin, [ref]$SFCMax, [ref]$SFCFlags )
#typecast values so we can do some math with them
$SFCMin = [long]$SFCMin
$SFCMax = [long]$SFCMax
$SFCFlags = [long]$SFCFlags
write-output "Return values from GetSystemFileCacheSize are: "
write-output "Function Result : $status"
write-output "            Min : $SFCMin"
write-output ("            Max : $SFCMax ( " + $SFCMax / 1024 / 1024 / 1024 + " GiB )")
write-output "          Flags : $SFCFlags"
output-flags $SFCFlags

Existe uma linha perto do topo que diz $MaxPercent = 12.5 que define o novo conjunto de trabalho máximo (memória ativa) para 12,5% da RAM física total. O Windows dimensionará dinamicamente a quantidade de dados do metarquivo na memória ativa com base nas demandas do sistema, portanto, não é necessário ajustar dinamicamente esse valor.

Isso não corrigirá nenhum problema que você tenha com o cache de arquivos mapeado ficando muito grande.

Também criei um script GetSystemFileCacheSize Powershell e postou no StackOverflow .

Editar: também devo salientar que você não deve executar nenhum desses dois scripts da mesma instância do Powershell mais de uma vez ou receberá o erro de que a chamada Add-Type já foi feita.

Editar: atualizou o script SetSystemFileCacheSize para a versão 1.1, que calcula um valor de cache máximo apropriado para você e tem um layout de saída de status mais agradável.

Editar: Agora eu atualizei meu laptop do Windows 7, posso dizer-lhe que o script é executado com sucesso no Windows 10, embora eu não tenha testado se ainda é necessário. Mas meu sistema ainda é estável, mesmo quando se move arquivos HDD de máquinas virtuais.

    
por 31.07.2013 / 02:50
4

Eu não afirmo ser um especialista em relação ao funcionamento interno da memória ou do cache de disco em um sistema operacional Windows, mas tenho duas observações:

  1. Se o sistema operacional não armazenasse em cache os dados na memória, ele teria que lê-lo do disco, que é uma memória de armazenamento exponencialmente mais lenta que a memória, então o problema de desempenho que você está vendo agora seria quase pior .

  2. Você está tentando resolver o problema tratando um sintoma do problema em vez da causa do problema. A causa do problema é quase certamente a falta de RAM física suficiente e minha sugestão seria resolver isso.

Além disso, embora o cache possa estar usando 1,5 GB de RAM, gostaria de saber qual é o uso da memória para outros processos e serviços, e a solução seria investigar esse uso para possíveis problemas.

    
por 27.10.2011 / 13:51
3

Para as pessoas que deram a solução óbvia, mas ineficaz, de apenas adicionar mais memória RAM, você claramente não lidou com esse problema em primeira mão.

Como afirmado por um poster anterior, não importa o quanto de memória RAM você jogue no problema ... tudo será preenchido. Estou executando uma ferramenta Atlassian no nosso servidor de aplicativos que foi migrado de 32 bits (2003) para 64 bits (2008). Ficou imediatamente aparente que houve uma perda de desempenho.

Ao olhar para o gerenciador de tarefas, quase toda a memória foi usada; mesmo que os processos em execução não reflitam isso. Quando aumentamos a memória de 8 GB para 16 GB, o problema também consumiu a memória adicional.

A única maneira de tratar o problema foi reiniciar o servidor, o que reduziu o uso de memória igual aos processos (cerca de 3,5 GB). Isso começou a subir novamente dentro de um dia ou mais.

Eu sabia que esse era um novo bug / recurso da Microsoft e fiquei feliz em encontrar este artigo. Eu amo como a Microsoft deixa este detalhe importantíssimo para os usuários descobrirem. Eu baixei o RamMap, que você acha que seria um utilitário nativo, e agora eu posso ver o uso do Metafile. Vamos configurar o cache para ser limpo a cada poucos dias e esperamos que isso resolva o problema.

É interessante que eu tenha visto esse problema apenas em um dos vários servidores migrados, por isso estou pensando se o metarquivo é alimentado apenas a partir de certos tipos de aplicativos.

    
por 22.05.2013 / 23:16
2

Esse problema pode ser resolvido de maneira rápida e gratuita usando a ferramenta SysInternals CacheSet. Simplesmente defina o máximo do conjunto de trabalho para um valor adequado menor que a quantidade de RAM do sistema e aplique.

    
por 08.10.2013 / 05:22
1

Desculpe ser tão direto, mas e quanto a você atualizar o servidor para uma quantidade de memória RAM que é um pouco maior do que as estações de trabalho nos dias de hoje? Memórias de 16GB são muito baratas. Menos caro do que meio dia do seu tempo.

    
por 27.10.2011 / 13:01
1

Aqui está um link para baixar a ferramenta Microsoft DynCache - não é necessário criar um ticket ou pagamento. link

(desculpas - só percebendo agora que isso não é para a versão R2)

O problema conhecido para o crescimento contínuo do cache é descrito aqui no blog da Microsoft: link

[atualização] correção de trabalho do Windows Server 2008 R2.

Eu encontrei código C # de amostra no Codeplex, rapidamente criei um projeto C # do console com o Visual Studio e compilei, trabalhei.

https://asstoredprocedures.svn.codeplex.com/svn/ASSP/FileSystemCache.cs

Note que você precisará adicionar uma referência ao Microsoft.AnalysisServices.AdomdClient, que pode ser encontrado aqui:

C:\Program Files (x86)\Microsoft.NET\ADOMD.NET

e comente o método ClearAllCaches () com (no meu caso) referências desnecessárias ao XMLaDiscover. Jogue isso no TaskScheduler.

    
por 23.08.2013 / 17:24
0

Você pode obter a ferramenta DynCache do MS que permitirá restringir o uso de RAM por metarquivo.

Clique aqui para obter a ferramenta do MS .

    
por 15.01.2013 / 16:14