Social Icons

11 de set de 2017

Como evitar o erro ORA-27102 limpando segmentos de memória após um crash da instância




    Você já teve problemas com um "crash" da instância em que foi necessário executar um shutdown abort, e quando você tentou subir ela de novo ocorreu o erro abaixo ou similar?
       ORA-27102: out of memory
       Linux-x86_64 Error: 28: No space left on device

     Isso normalmente acontece quando os segmentos de memória alocados pela instância "abortada" não foram devidamente liberados no Sistema Operacional (SO). Já vi isso acontecer algumas vezes ao longo de 10 anos trabalhando como DBA, mas por falta de conhecimentos avançados em Linux (meu e dos SysAdmins que administram os servidores de produção) para verificar/resolver o problema de forma definitiva, tive que reduzir o tamanho da SGA para que fosse possível subi-la novamente, de forma rápida, sem ter que reiniciar a máquina (algo que não poderia ser feito naquele momento).

    Recentemente, outro DBA que trabalha comigo, teve o mesmo problema em um BD de produção, ao tentar restartá-lo após alterar um parâmetro estático da instância. Ele agendou a execução de um shutdown immediate + startup no período da manhã, pouco antes dele chegar, e quando chegou, viu que o shutdown estava "congelado". O que ele fez então? Abriu uma nova sessão no SQL Plus e executou um shutdown abort. Em seguida, ao tentar fazer o startup ele viu o erro ORA-27102 e me mandou uma mensagem via WhatsApp perguntando se eu sabia o que fazer ou já tinha visto aquele erro. Eu estava online, rapidamente vi a mensagem e lhe expliquei o problema, além de sugerir a solução emergencial que eu já havia executado outras vezes. Porém, dessa vez, estando com mais conhecimentos em Linux (conforme mencionado no artigo Livro "Linux Recipes for Oracle DBAs), consegui resolvê-lo depois! Como resolvi o problema? Aprendi a descobrir quais segmentos de memória da instância que havia dado crash ainda estavam "presos" no SO, para eliminá-los em seguida.

     Seguem abaixo informações sobre o ambiente e um roteiro com mais detalhes sobre como executar essa atividade:
  
Detalhes do ambiente em que o problema ocorreu
     - SO "Red Hat Enterprise Linux Server release 5.8"
     - Banco de Dados "Oracle Enterprise 11.2.0.4". 


Passo 1: Verificando o alert log:
     A 1ª coisa que fiz foi ler o alert log da instância em que foi executado o shutdown abort, para entender o que realmente ocorreu antes do colega DBA ter executado o último startup:

Mon Jul 25 07:45:01 2017
ALTER SYSTEM SET XXX='YYY' SCOPE=SPFILE;
Shutting down instance (immediate)
Stopping background process SMCO
Shutting down instance: further logons disabled
Mon Jul 25 07:45:42 2017
kkjcre1p: unable to spawn jobq slave process, slot 0, error 1089
kkjcre1p: unable to spawn jobq slave process, slot 0, error 1089
Stopping background process CJQ0
Mon Jul 25 07:45:49 2017
Stopping background process QMNC
Stopping background process MMNL
Stopping background process MMON
Mon Jul 25 07:50:52 2017
Active call for process 4250 user 'oraora' program 'oraora@maquina'
SHUTDOWN: waiting for active calls to complete.
Mon Jul 25 08:21:01 2017
USER (ospid: 31273): terminating the instance
Instance terminated by USER, pid = 31273
Mon Jul 25 08:21:08 2017
Starting ORACLE instance (normal)
  
     No trecho acima extraído do alert log (com dados confidenciais e datas descaracterizados), pude tirar as seguintes conclusões;
          - Às 07:45:01 foi alterado um parâmetro, e em seguida foi executado um shutdown immediate;
          -Das 07:50:52 até 08:21:01 a instância ficou congelada esperando algum(ns) processo(s) ativo(s) completar(em) sua(s) execução(ões), para então finalizar o shutdown;
          - Às 08:21:01 o DBA executou um shutdown abort (ver linha "USER (ospid: 31273): terminating the instance") e neste momento a instância realmente finalizou;
          - Por fim, às 08:21:08, ele tentou executar um startup e viu o erro abaixo:
  
     

     Pesquisando um pouco mais no alert, verifiquei que ele só conseguiu realmente "startar" a instância depois de diminuir o tamanho da sua SGA, considerando a qtde. de memória RAM livre no sistema.
  

Passo 2: Identificando os segmentos de memória existentes:
     Já sabemos o que realmente aconteceu na máquina e que a instância está no ar novamente, porém os segmentos de memória da SGA e PGA da instância "abortada" ficaram presos no SO, sem uso, ocupando uma grande qtde. de memória desnecessariamente. Para eliminar estes segmentos de memória "presos" (sem reiniciar a máquina), visando liberar o espaço em RAM que eles ocupam, precisamos descobrir previamente o identificador deles, executando o comando ipcs em uma janela de terminal do Linux, como no exemplo abaixo:

     $ ipcs -m
  
------ Shared Memory Segments --------
key        shmid    owner perms  bytes       nattch  status
0x00000000
11875830 oraora 640   402653184   166     dest
0x00000000
11700699 oraora 640   55431921664 166     dest
0x00000000
19011529 oraora 640   4096        0       dest

     No resultado do comando executado acima, os principais valores que precisamos checar são:
         1- Coluna "shmid": o identificador (shmid) dos segmentos de memória;
         2- Coluna "owner": o usuário que criou os segmentos de memória. Traduzindo: o usuário do SO que executou o startup na instância;
         3- Coluna "bytes": o tamanho de cada segmento;
         4- Coluna "status": Status do segmento. Quando a instância está ativa normalmente você verá o valor dessa coluna vazio. Quando ela não está mais ativa, como no exemplo acima, você poderá ver o valor dest (destroyed) indicando que o segmento de memória está marcado para ser destruído, mas algo ainda está sendo processado e impedindo ou postergando a sua eliminação.

     Para cada instância configurada com memória ASMM existem normalmente 3 segmentos: o maior normalmente é relacionado à SGA (55431921664 bytes), o segundo maior é relacionado à PGA (402653184), e o terceiro, de apenas 4k (4096 bytes), pesquisei mas encontrei ainda algo dizendo exatamente do que se trata.

     Na máquina de produção em que eu estava executando este roteiro, existem 3 BDs de produção, portanto, a execução do comando "ipcs -m" retornou 12 linhas, sendo que, eu omiti aqui 9 delas para focar apenas nas informações correspondentes ao problema ocorrido. Os dados reais também foram alterados com valores fictícios, com o objetivo de não expor dados confidenciais da empresa.
  
     Executaremos agora, mais uma vez o comando ipcs, porém passando o parâmetro -mt, para ver algumas informações adicionais sobre os segmentos de memória, como por exemplo, a data/hora em que foram iniciadas (attached) e finalizadas (detached) alterações neles (entenderemos melhor o porquê disso no final desse item):

     $ ipcs -mt

------ Shared Memory Attach/Detach/Change Times --------
shmid      owner      attached             detached             changed
11875830   oraora     Apr 25 08:21:03    Apr 25 08:21:03     Jan 20 21:56:47
11700699   oraora     Apr 25 08:21:03    Apr 25 08:21:03     Jan 20 21:56:47
19011529   oraora     Jul 10 08:21:03    Jul 10 08:21:03     Apr 23 21:56:47


     Por fim, iremos executar novamente o comando ipcs, com o parâmetro -mp, para ver o número do último processo (lpid) que executou alguma alteração nos segmentos de memória:

     $ ipcs -mp

------ Shared Memory Creator/Last-op --------
shmid      owner      cpid       lpid
11875830   oraora     11603      31273
11700699   oraora     11603      31273
19011529   oraora     11603      31273

    O mais importante neste passo, após executar os 3 comandos acima, é consultar os identificadores (shmid) dos segmentos de memória (que serão utilizados no próximo passo) e validar as informações adicionais, tais como as datas/horas em que os segmentos de memória foram criados e alterados pela última vez, e o número do último processo do SO que fez alguma alteração neles.

Exemplo: Em uma instância ativa, tanto o valor de lpid (ver "ipcs -mp") quanto o valor de attached e detached  (ver "ipcs -mt") mudam a todo momento (sempre que o processo de uma sessão do BD comanda alguma alteração nas áreas de memória do Oracle), portanto, se você executar estes comandos em diversos momentos e os valores mencionados nunca mudarem, você terá um forte indício de que os segmentos de memória correspondentes não estão em uso. Para ter certeza, olhe também se o valor da coluna "status" é igual "dest" e execute o próximo passo.


Passo 3: Identificando/eliminando os segmentos de memória que deverão ser eliminados:
     Após descobrir os identificadores de segmentos de memória existentes, verificaremos quais deles ficaram presos após o crash da instância, para serem eliminados em seguida. Para isso, devemos configurar o ORACLE_SID da instância desejada, e em seguida, executar o comando sysresv, que irá mostrar entre diversas informações, o ID dos segmentos de memória dessa instância, como no exemplo abaixo executado em uma instância com memória ASMM configurada:

     $ export ORACLE_SID=oracle
     $ sysresv

IPC Resources for ORACLE_SID "oracle" :
Shared Memory:
ID              KEY
215133889       0x00000000
234545668       0x00000000
243265448       0x9e7dca78
Semaphores:
ID              KEY
1989563         0x9d53ab20
Oracle Instance alive for sid "oracle"


     O resultado acima indica entre diversas informações, o ID dos segmentos de memória de uma instância ativa. Se não existir instância para o ORACLE_SID configurado, o resultado deverá similar ao que vemos abaixo:

IPC Resources for ORACLE_SID "oracle" :
Shared Memory
ID              KEY
No shared memory segments used
Semaphores:
ID              KEY
No semaphore resources used
Oracle Instance not alive for sid "oracle"


     No caso deste artigo, como temos 3 bancos de dados de produção existentes na mesma máquina, tive que configurar o ORACLE_SID 3 vezes, uma para cada instância, e executar o "sysresv" para checar quais segmentos de memória ainda estavam ativos. Como certifiquei quais eram os IDs dos segmentos que deviam ser eliminados? Simples, relacionei o ID de cada linha de retorno do sysresv com os IDs de segmentos retornados pelos comandos ipcs, as linhas que sobraram no resultado do "ipcs" foram aquelas correspondentes aos segmentos presos da instância abortada, que eliminaremos no próximo passo. Veja o exemplo abaixo com dados fictícios, executando o sysresv para as 3 instâncias de uma máquina. Os segmentos de memória que não pertenciam a nenhuma delas está destacado em vermelho no resultado do comando "ipcs -m" e são eles que devemos eliminar no próximo passo:

ipcs -m  

------ Shared Memory Segments --------
key         shmid   owner perms bytes       nattch   status
0x00000000 12345678 oraora 640  333333333   166
0x00000000 13325489 oraora 640  66666666666 166
0x00000000 15482223 oraora 640  4096        0
0x00000000 12222222 oraora 640  111111111   166
0x00000000 11777777 oraora 640  444444444   166
0x00000000 18955555 oraora 640  4096        0
0x00000000 11875830 oraora 640  402653184   166      dest
0x00000000 11700699 oraora 640  55431921664 166      dest
0x00000000 19011529 oraora 640  4096        0        dest

0x00000000 18777775 oraora 640  402653184   166
0x00000000 19555555 oraora 640  55431921664 166
0x00000000 19878787 oraora 640  4096        0


$ export ORACLE_SID=a
$ sysresv

  
IPC Resources for ORACLE_SID "a" :
Shared Memory:
ID       KEY
12345678 0x00000000
13325489 0x00000000
15482223 0x9e7dca78
Semaphores:
ID       KEY
1159563  0x9d53ab20
Oracle Instance alive for sid "a"

   

$ export ORACLE_SID=b
$ sysresv


IPC Resources for ORACLE_SID "b" :
Shared Memory:
ID       KEY
12222222 0x00000000
11777777 0x00000000
18955555 0x9e7dca78
Semaphores:
ID       KEY
1989563  0x9d53ab20
Oracle Instance alive for sid "b"

$ export ORACLE_SID=c
$ sysresv

   
IPC Resources for ORACLE_SID "c" :
Shared Memory:
ID       KEY
18777775 0x00000000
19555555 0x00000000
19878787 0x9e7dca78
Semaphores:
ID       KEY
1989563  0x9d53ab20
Oracle Instance alive for sid "c"

  
     Antes de eliminar os segmentos identificados em vermelho, execute também os demais comandos "ipcs" que vimos anteriormente, para ver os valores de lpid, attached e detached dos segmentos, e ter certeza de que eles realmente não estão em uso.

     Agora que já sabemos quais segmentos de memória devemos eliminar, precisamos verificar se existe algum processo do SO que ficou congelado, usando-os ou prendendo-os, e impedindo-os de serem eliminados. Para isso devemos executar o comando "grep xxx /proc/*/maps", substituindo xxx pelo ID dos segmentos de memórias a serem eliminados. Se o comando retornar algo, significa que há processo(s) utilizando o segmento de memória. O(s) número(s) desse(s) processo(s) pode(m) ser identificado(s) através dos valores em verde destacados no resultado do exemplo abaixo:

$ grep 11700699/proc/*/maps

/proc/3156/maps:7fd50c70a000-7fd50c76a000 rw-s 00000000 00:04 2916359 ...
/proc/3364/maps:7f028b283000-7f028b2e3000 rw-s 00000000 00:04 2916359 ...           

     O resultado poderá conter uma ou mais linhas com valores contendo o padrão "/proc/nnnn/maps", onde nnnn corresponde ao número do processo que está utilizando o segmento de memória. Neste caso, pesquise mais no SO sobre o número deste processo para ter certeza de que você poderá eliminá-lo, e finalizar o seu trabalho, eliminando em seguida, o segmento de memória relacionado. Na maioria das vezes, ao eliminar este(s) processo(s), o segmento de memória será eliminado automaticamente. No meu caso, quando executei o comando  "grep xxx /proc/*/maps" passando como valor para xxx o ID do segmento de memória que era correspondente à SGA (o de maior tamanho entre aqueles que estavam "presos" e não utilizados), foi retornado um valor que indicava o número de um único processo que estava ocupando o segmento de memória. Pesquisei sobre este processo no SO e descobri que ele estava congelado tentando gravar um core dump cujo arquivo já havia sido apagado. Como isso ocorreu? Tivemos um erro há alguns meses atrás no BD, que estava gerando core dumps gigantes, que por sua vez, estavam enchendo o disco onde eles estavam sendo gravados. Como medida de emergência eles foram apagados manualmente, e isso causou algum crash neste processo que estava gerando-os. Após identificar o que fazia este processo, tive certeza de que eu podia matá-lo com o famoso "kill -9", e meu problema foi resolvido, a memória RAM ocupada pelos segmentos de memória "perdidos" foi liberada para o SO novamente. Se isso não tivesse ocorrido, o próximo passo para eliminar os "segmentos perdidos" seria executar o comando abaixo substituindo xxx pelo ID deles: 

     $ ipcrm shm xxx

       
     Bom pessoal, por hoje é só! Espero que vocês tenham gostado e aprendido algo mais!
     Qualquer dúvida que tiver, é só deixar um comentário.
  
[]s


Referências:
     - Removing Shared Memory, Red Hat;
     - Linux Recipes for Oracle DBAs, Editora Apress.
  

0 comentários:

Postar um comentário

 

Suporte remoto alunos

Seguidores

Meu One Drive (antigo Sky Drive)