ERROR: invalid memory alloc request size 1818585462
Depurar estos problemas no es fácil, porque hay que encontrar la ubicación del dato que está corrupto para poder tomar alguna acción con él. Típicamente se le pide al usuario que haga búsqueda binaria sobre la tabla, usando las claúsulas LIMIT y OFFSET de manera de restringir el conjunto de valores hasta encontrar exactamente qué registro o registros están corruptos. Este método es muy lento.
Para resolver este problema, Enova Financial pidió a Command Prompt que desarrollara un mecanismo para encontrar fácil y rápidamente aquellos datos que presentaban algún nivel de corrupción. El resultado es la función que está al final de este artículo.
Muy a grandes rasgos, lo que hace la función de abajo es, dado el nombre de una tabla, escribir otra función que recorre dicha tabla y reporta el CTID (posición física) y los valores de llave primaria de todos aquellos registros que fallan un control de sanidad básico.
Una vez que el o los registros han sido identificados, es cuestión de determinar qué se quiere hacer con el valor corrupto. Una posibilidad es hacer un UPDATE del valor de esa columna a NULL. Otra posibilidad es hacer un UPDATE del valor correcto (cosa que puede ser difícil de determinar); una tercera sería eliminar (DELETE) el registro completamente. Aún una cuarta sería intentar depurar más finamente el sistema para encontrar cuál es el valor correcto que debería haber. Esto último queda de ejercicio para el lector.
A continuación, el código de la meta-función:
create or replace function toastcheck_writer(text) returns void language plpgsql as $ff$
declare
func text;
funcname text;
column record;
pkc record;
indent text;
colrec record;
pkcols text;
pkformat text;
pk_col_ary text[];
begin
pkcols = '';
pkformat = '';
pk_col_ary = '{}';
funcname = 'toastcheck__' || $1;
FOR pkc IN EXECUTE $f$ SELECT attname
FROM pg_attribute JOIN
pg_class ON (oid = attrelid) JOIN
pg_index on (pg_class.oid = pg_index.indrelid and attnum = any (indkey))
WHERE pg_class.oid = '$f$ || $1 || $f$ '::regclass and indisprimary $f$
LOOP
IF pkcols = '' THEN
pkcols = quote_ident(pkc.attname);
pkformat = '%';
ELSE
pkcols = pkcols || ', ' || quote_ident(pkc.attname);
pkformat = pkformat || ', %';
END IF;
pk_col_ary = array_append(pk_col_ary, quote_ident(pkc.attname));
END LOOP;
/*
* This is the function header. It's basically a constant string, with the
* table name replaced a couple of times and the primary key columns replaced
* once. Make sure we don't fail if there's no primary key.
*/
IF pkcols <> '' THEN
pkcols = ', ' || pkcols;
pkformat = ', PK=( ' || pkformat || ' )';
END IF;
func = $f$
CREATE OR REPLACE FUNCTION $f$ || funcname || $f$() RETURNS void LANGUAGE plpgsql AS $$
DECLARE
rec record;
BEGIN
FOR rec IN SELECT ctid $f$ || pkcols || $f$ FROM $f$ || $1 || $f$ LOOP
DECLARE
f record;
l int;
BEGIN
SELECT * INTO f FROM $f$ || $1 || $f$ WHERE ctid = rec.ctid;
-- make sure each column is detoasted and reported separately
$f$;
/* We now need one exception block per toastable column */
indent = ' ';
FOR column in SELECT attname
FROM pg_attribute JOIN pg_class on (oid=attrelid)
WHERE pg_class.oid = $1::regclass and attlen = -1
LOOP
func := func || indent || E'BEGIN\n';
func := func || indent || $f$ SELECT length(f.$f$ ||
quote_ident(column.attname) || E') INTO l;\n';
/* The interesting part here needs some replacement of the PK columns */
func := func || indent || $f$EXCEPTION WHEN OTHERS THEN
RAISE NOTICE 'TID %$f$ || pkformat || $f$, column "$f$ || column.attname || $f$": exception {{%}}',
rec.ctid, $f$;
/* This iterates zero times if there are no PK columns */
FOR colrec IN SELECT f.i[a] AS pknm
FROM (select pk_col_ary as i) as f,
generate_series(array_lower(pk_col_ary, 1), array_upper(pk_col_ary, 1)) as a
LOOP
func := func || $f$ rec.$f$ || colrec.pknm || $f$, $f$;
END LOOP;
func := func || E'sqlerrm;\n';
func := func || indent || E'END;\n';
END LOOP;
/* And this is our constant footer */
func := func || $f$
END;
END LOOP;
END;
$$;
$f$;
EXECUTE func;
RAISE NOTICE $f$Successfully created function %()$f$, funcname;
RETURN;
END;
$ff$;