O PostgreSQL DELETE FROM falha com o `Erro: tentativa de excluir a tupla invisível`

25

O erro

Tentando excluir tuplas contendo carimbos de data / hora inválidos com

DELETE FROM comments WHERE date > '1 Jan 9999' OR date < '1 Jan 2000' OR date_found > '1 Jan 9999' OR date_found < '1 Jan 2000';

termina em

ERROR:  attempted to delete invisible tuple

uma lista de discussão de 2009 discutindo exatamente a mesma mensagem de erro, onde a OP a corrigiu, mas não encontro explicações sobre como ele fez ou o que poderia ter levado a esse erro.

Estou desamparado devido à falta de acessos no Google e ao meu conhecimento limitado do PostgreSQL.

O que levou à corrupção

Eu tive um servidor PostgreSQL 9.5.5 ( ~ 4 TB de dados, todas as configurações padrão, exceto limites de memória aumentados) ) rodando no Debian 8, quando o kernel do sistema operacional entrou em pânico - provavelmente durante a reconstrução de / dev / md1 onde o swap estava localizado. Antes disso, o PostgreSQL consumia quase todo o espaço em disco com um arquivo de log de 400 GB. O sistema operacional nunca inicializou novamente, as verificações de disco estavam OK, então eu inicializei a partir de um LiveCD e fiz o backup de cada dispositivo de bloco em imagens, apenas por precaução. Eu reconstruí com êxito / diretório de / dev / md2, o fsck mostrou um sistema de arquivos limpo e fiz backup da pasta PGDATA em um disco rígido externo.

O que eu fiz para tentar recuperar

Depois de formatar os dispositivos md e reinstalar o SO com um novo postgresql-9.5, parei o servidor PostgreSQL, movi e joguei a pasta PGDATA para o usuário do postgres e iniciei o servidor - tudo parecia bem, não havia erros.

Assim que comecei pg_dumpall, morreu com

Error message from server: ERROR:  timestamp out of range

Naturalmente, tentei excluir as tuplas ofensivas, apenas para acabar com o mesmo invisible tupleerro repetidamente.

Coisas que tentei

Primeiro, as consultas DELETE falharam devido a páginas danificadas, por isso defini as seguintes configurações:

zero_damaged_pages = on
ignore_system_indexes = on
enable_indexscan = off
enable_bitmapscan = off
enable_indexonlyscan = off

Agora, notei que, quando executo as mesmas consultas novamente, o servidor zera as mesmas páginas repetidamente, sem ter certeza do que isso significa:

invalid page in block 92800 of relation base/16385/16443; zeroing out page

Eu tentei seguir em uma ordem indefinida:

  • pg_resetxlog -D $PGDATA fez o seu trabalho sem erros ou mensagens
  • Excluídos todos os índices, incluindo restrições de pkey
  • CREATE TABLE aaa AS (SELECT * FROM comments);leva a Segmentation faultem

    heap_deform_tuple (tuple=tuple@entry=0x7f0d1be29b08, tupleDesc=tupleDesc@entry=0x7f0d1a35abe0, values=values@entry=0x7ffd57a5beb0, isnull=isnull@entry=0x7ffd57a65af0 "\001\001") É reproduzível e deixa um núcleo de ~ 9 GB.

  • SELECT COUNT(*) from comments;permitido VACUUM comments;concluir, o mesmo truque não funciona em outras tabelas.
  • SELECT COUNT(*) from photos;e VACUUM photos;agora morre com ERROR: MultiXactId 302740528 has not been created yet -- apparent wraparound- este assombra todas as tabelas, onde outros erros não aparecem mais.

Pensamentos

  • O DB estava sendo martelado por muitas gravações ( possivelmente duplicadas ) com a ON CONFLICTcláusula DB estava fazendo um VACUUMquando ocorreu o pânico do kernel, acredito que é o que resta dele que está causando problemas nonexistent MultiXactIdseinvisible tuple
  • Os dados foram coletados com o rastreador ao longo de mais de 2 anos e estou totalmente de acordo em perder alguns deles
  • Agora eu faço backups
  • Não havia restrições relacionais entre tabelas nem gatilhos

Aqui está a saída pg_controldata a partir de agora:

pg_control version number:            942
Catalog version number:               201510051
Database system identifier:           6330224129664261958
Database cluster state:               in production
pg_control last modified:             Thu 08 Dec 2016 01:06:22 AM EET
Latest checkpoint location:           1562/8F9F8A8
Prior checkpoint location:            1562/8F7F460
Latest checkpoint's REDO location:    1562/8F9F8A8
Latest checkpoint's REDO WAL file:    000000010000156200000008
Latest checkpoint's TimeLineID:       1
Latest checkpoint's PrevTimeLineID:   1
Latest checkpoint's full_page_writes: on
Latest checkpoint's NextXID:          0/40781255
Latest checkpoint's NextOID:          67798231
Latest checkpoint's NextMultiXactId:  1
Latest checkpoint's NextMultiOffset:  0
Latest checkpoint's oldestXID:        615
Latest checkpoint's oldestXID's DB:   1
Latest checkpoint's oldestActiveXID:  0
Latest checkpoint's oldestMultiXid:   1
Latest checkpoint's oldestMulti's DB: 1
Latest checkpoint's oldestCommitTsXid:0
Latest checkpoint's newestCommitTsXid:0
Time of latest checkpoint:            Thu 08 Dec 2016 01:06:22 AM EET
Fake LSN counter for unlogged rels:   0/1
Minimum recovery ending location:     0/0
Min recovery ending loc's timeline:   0
Backup start location:                0/0
Backup end location:                  0/0
End-of-backup record required:        no
wal_level setting:                    minimal
wal_log_hints setting:                off
max_connections setting:              100
max_worker_processes setting:         8
max_prepared_xacts setting:           0
max_locks_per_xact setting:           64
track_commit_timestamp setting:       off
Maximum data alignment:               8
Database block size:                  8192
Blocks per segment of large relation: 131072
WAL block size:                       8192
Bytes per WAL segment:                16777216
Maximum length of identifiers:        64
Maximum columns in an index:          32
Maximum size of a TOAST chunk:        1996
Size of a large-object chunk:         2048
Date/time type storage:               64-bit integers
Float4 argument passing:              by value
Float8 argument passing:              by value
Data page checksum version:           0

Atualizações

  • ( 9 de dezembro de 2016 ) Ao ler sobre MultiXactIds inexistentes , lembrei-me de que meu banco de dados não estava sob carga operacional no momento do travamento, mas estava processando uma VACUUMsolicitação manual . Coloquei os servidores da Web e os rastreadores offline depois que percebi que havia apenas 3% de espaço restante nos discos. Eu deveria ter verificado se /var/loghá arquivos grandes, mas culpei o PostgreSQL por engano e tentei VACUUM FULL, apenas para encontrá-lo abortado devido ao pouco espaço restante no dispositivo. Então eu comecei o VACUUM comum e deixei por isso mesmo.
  • ( 14 de dezembro de 2016 ) Baixou uma ramificação 9.5 das fontes do PostgreSQL do Github, comentou os blocos no heapam.c e no multixact.c e o compilou com a esperança de que não gerasse esses erros. Mas o servidor não foi iniciado, pois precisava ser configurado com os mesmos sinalizadores usados ​​em um que eu tinha do APT. Havia cerca de 47 bandeiras, cada uma exigindo uma dependência com nome não óbvio, então desisti dessa ideia.
  • ( 16 de dezembro de 2016 ) Encontrei uma maneira de livrar-se de tuplas com timestamps inválidos zerando as páginas relevantes. Primeiro, defino as seguintes opções em psql:

    \set FETCH_COUNT 1
    \pset pager off

    Eu faço então SELECT ctid, * FROM comments;. Dessa forma, ele cospe ctiduma tupla incorreta antes que a consulta morra. Em seguida, prossigo para preencher essa página com zeros: dd if=/dev/zero of=/var/lib/postgresql/9.5/main/base/16385/16443 bs=8K seek=92803 count=1 conv=notruncmas cada página, zerada dessa maneira, quebra a página anterior, resultando em uma página 16442agora com uma tupla com carimbo de data / hora inválido. Não tenho certeza do que estou fazendo de errado aqui.

  • ( 16 de dezembro de 2016 ) A tentativa pg_dump -Fc --table photos vw > photos.bakresulta em falha de segmentação após a gravação de 1,3 GB ( provavelmente de 800 GB ). Aqui está o log do servidor:

    2016-12-16 18:48:05 EET [19337-2] LOG:  server process (PID 29088) was terminated by signal 11: Segmentation fault
    2016-12-16 18:48:05 EET [19337-3] DETAIL:  Failed process was running: COPY public.photos (id, owner_id, width, height, text, date, link, thumb, album_id, time_found, user_id, lat, long) TO stdout;
    2016-12-16 18:48:05 EET [19337-4] LOG:  terminating any other active server processes
    2016-12-16 18:48:05 EET [19342-2] WARNING:  terminating connection because of crash of another server process
    2016-12-16 18:48:05 EET [19342-3] DETAIL:  The postmaster has commanded this server process to roll back the current transaction and exit, because another server process exited abnormally and possibly corrupted shared memory.
    2016-12-16 18:48:05 EET [19342-4] HINT:  In a moment you should be able to reconnect to the database and repeat your command.
    2016-12-16 18:48:05 EET [19337-5] LOG:  all server processes terminated; reinitializing
    2016-12-16 18:48:06 EET [29135-1] LOG:  database system was interrupted; last known up at 2016-12-14 22:58:59 EET
    2016-12-16 18:48:07 EET [29135-2] LOG:  database system was not properly shut down; automatic recovery in progress
    2016-12-16 18:48:07 EET [29135-3] LOG:  invalid record length at 1562/A302F878
    2016-12-16 18:48:07 EET [29135-4] LOG:  redo is not required
    2016-12-16 18:48:07 EET [29135-5] LOG:  MultiXact member wraparound protections are now enabled
    2016-12-16 18:48:07 EET [19337-6] LOG:  database system is ready to accept connections
    2016-12-16 18:48:07 EET [29139-1] LOG:  autovacuum launcher started

    Aqui está um pequeno stacktrace:

    #0  pglz_decompress (source=source@entry=0x7fbfb6b99b13 "32;00/0ag4d/Jnz\027QI\003Jh3A.jpg", slen=<optimized out>,
        dest=dest@entry=0x7fbf74a0b044 "", rawsize=926905132)
    #1  0x00007fc1bf120c12 in toast_decompress_datum (attr=0x7fbfb6b99b0b)
    #2  0x00007fc1bf423c83 in text_to_cstring (t=0x7fbfb6b99b0b)

    Não faço ideia de como contornar isso.

  • ( 29 de dezembro de 2016 ) Eu escrevi um utilitário que faz isso SELECT * FROM tablename LIMIT 10000 OFFSET 0, aumentando o deslocamento e diminuindo as tuplas mortas, e ele duplicou com êxito os dados na minha máquina local, exceto as tuplas ( espero que as únicas ) que corrompi manualmente. Também deve esperar se o servidor reiniciar. No entanto, eu não tinha espaço suficiente no meu RAID e criei um espaço slowdiskde tabela em um disco rígido de 8 TB. Quando tento CREATE DATABASE vwslow WITH TABLESPACE slowdisk, isso não ocorre com erros:

    2016-12-29 02:34:13 EET [29983-1] LOG:  request to flush past end of generated WAL; request 950412DE/114D59, currpos 1562/A3030C70
    2016-12-29 02:34:13 EET [29983-2] CONTEXT:  writing block 58368001 of relation base/16385/16473
    2016-12-29 02:34:13 EET [29983-3] ERROR:  xlog flush request 950412DE/114D59 is not satisfied --- flushed only to 1562/A3030C70
    2016-12-29 02:34:13 EET [29983-4] CONTEXT:  writing block 58368001 of relation base/16385/16473
    2016-12-29 02:34:13 EET [30005-44212] postgres@vw ERROR:  checkpoint request failed
    2016-12-29 02:34:13 EET [30005-44213] postgres@vw HINT:  Consult recent messages in the server log for details.
    2016-12-29 02:34:13 EET [30005-44214] postgres@vw STATEMENT:  CREATE DATABASE vwslow WITH TABLESPACE slowdisk;

    Manual CHECKPOINTresultou nos mesmos erros.

    Uma reinicialização do servidor fez com que o erro do ponto de verificação desaparecesse e deixe-me executar minha ferramenta. Responderei à minha pergunta e publicará o código, se funcionar.

Kai
fonte
Leia isso e aja antes de tentar fazer qualquer outra coisa: wiki.postgresql.org/wiki/Corruption . Embora pareça que é um pouco tarde. Eu suspeito fortemente que os problemas de disco / RAID reconstruídos sejam a causa raiz aqui.
Craig Ringer
Você manteve uma cópia do diretório de dados antes de fazer um resetxlog etc?
Craig Ringer
Não é o próprio datadir, mas movi as imagens de disco bruto para fora do local, onde elas são mais seguras. Eles estão bem, porque eu reconstruí meu RAID deles.
Kai
11
@CraigRinger você escreverá uma resposta sobre isso? Você provavelmente é um dos poucos usuários que responde na tag do Postgres e pode dizer algo útil sobre o assunto. Parece que muito pouco pode ser feito.
ypercubeᵀᴹ
4
Você não encontrará a resposta nesta questão. Erwin pode ser capaz de ajudá-lo. Além disso, procure David Fetter ou AndrewSW / RhodiumToad em irc.freenode.net/#postgresql. Diga-nos (dba.se) o que eles encontram. Sinto que este será um trabalho de consultoria pago que exigirá acesso total ao seu banco de dados. developer.postgresql.org/~adunstan linkedin.com/in/davidfetter Não tenho afiliação com nenhum desses caras ou com suas empresas. Mas, eles são os únicos em quem eu pessoalmente confiaria para sair desse problema.
Evan Carroll

Respostas:

2

Bem, eu consegui automatizar o processo de recuperação SELECTe INSERT INTO, pulando intervalos e aguardando se o servidor travasse. Eu o codifiquei primeiro no Node - ele extraiu dados não danificados commentse ainda continua.

Ontem decidi experimentar o Golang, e aqui está um repositório com o código Go: https://github.com/kaivi/pg_ripper Atualizá-lo-emos em breve para que ele realmente funcione em torno de tuplas ruins e não desista de todo intervalo contendo um.

Kai
fonte