Diagnóstico y recuperación de OAI-PMH Harvesting en DSpace 7.6

Por Staff INFODI
17 minutos
Diagnóstico y recuperación de OAI-PMH Harvesting en DSpace 7.6

En una plataforma DSpace 7.6 que utiliza OAI-PMH para recolectar contenidos desde un repositorio DSpace externo, detectamos que varias colecciones habían dejado de actualizarse correctamente.

El caso resultó particularmente interesante porque durante el diagnóstico aparecieron varios problemas diferentes que, a primera vista, podían parecer parte de una misma falla: colecciones bloqueadas para harvesting, incompatibilidades de esquemas de metadatos, errores durante una restauración completa y diferencias entre los registros almacenados en PostgreSQL y los mostrados por la interfaz.

En este artículo documentamos el diagnóstico y las acciones realizadas, utilizando identificadores y dominios ficticios para no exponer información de las plataformas involucradas.

1. El problema inicial

Al intentar ejecutar manualmente el harvesting de una colección:

[dspace@localhost ~]$ /opt/dspace/bin/dspace harvest \
-r \
-e administrador@example.org \
-c 20.500.00000/LOREM01

DSpace respondía:

The script has started
Running: a harvest cycle on 20.500.00000/LOREM01
Initializing the harvester...
Initializing the harvester failed.

java.lang.IllegalStateException: Unable to harvest

Caused by: org.dspace.harvest.HarvestingException:
Provided collection is not set up for harvesting

A primera vista, el mensaje indicaba que la colección no estaba configurada para realizar harvesting.

Sin embargo, la configuración visible desde la administración de DSpace mostraba que la colección sí recolectaba contenido desde una fuente externa.

Por lo tanto, fue necesario revisar directamente la base de datos.

2. Verificación de la colección

Primero identificamos el UUID correspondiente al Handle de la colección:

SELECT d.uuid, h.handle
FROM dspaceobject d
JOIN handle h
    ON h.resource_id = d.uuid
WHERE h.handle = '20.500.00000/LOREM01';

Con el UUID obtenido pudimos revisar la configuración almacenada en harvested_collection:

SELECT *
FROM harvested_collection
WHERE collection_id = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';

La colección efectivamente tenía configurados:

  • proveedor OAI-PMH;
  • identificador del set OAI;
  • formato de metadatos;
  • modalidad de harvesting;
  • fecha del último harvesting;
  • estado del proceso.

Sin embargo, encontramos un dato especialmente relevante:

harvest_status  = -1
harvest_message = Unknown error occurred while generating an OAI response

La colección estaba correctamente configurada, pero había quedado almacenada en un estado de error.

3. Verificación masiva de colecciones

Como el problema afectaba a más de una colección, utilizamos una consulta para revisar varias simultáneamente:

SELECT
    h.handle,
    d.uuid,
    hc.id,
    hc.harvest_type,
    hc.oai_source,
    hc.oai_set_id,
    hc.metadata_config_id,
    hc.harvest_status,
    hc.harvest_message,
    hc.last_harvested
FROM handle h
JOIN dspaceobject d
    ON d.uuid = h.resource_id
LEFT JOIN harvested_collection hc
    ON hc.collection_id = d.uuid
WHERE h.handle IN (
    '20.500.00000/LOREM01',
    '20.500.00000/LOREM02',
    '20.500.00000/LOREM03',
    '20.500.00000/LOREM04'
)
ORDER BY h.handle;

El patrón se repetía:

Handle                    Status   Mensaje
------------------------  ------   -----------------------------------------------
20.500.00000/LOREM01      -1       Unknown error occurred while generating...
20.500.00000/LOREM02      -1       Unknown error occurred while generating...
20.500.00000/LOREM03      -1       Unknown error occurred while generating...
20.500.00000/LOREM04      -1       Unknown error occurred while generating...

Esto explicaba por qué los intentos posteriores podían terminar con:

Provided collection is not set up for harvesting

El mensaje podía inducir a pensar que faltaba configurar el proveedor OAI, cuando el problema real era el estado en que había quedado el harvesting después de una ejecución fallida.

4. Restablecimiento del estado

Para permitir un nuevo intento, restablecimos únicamente las colecciones que estaban en estado -1.

UPDATE harvested_collection hc
SET
    harvest_status = 0,
    harvest_message = 'Ready'
FROM handle h
WHERE h.resource_id = hc.collection_id
  AND h.handle IN (
      '20.500.00000/LOREM01',
      '20.500.00000/LOREM02',
      '20.500.00000/LOREM03',
      '20.500.00000/LOREM04'
  )
  AND hc.harvest_status = -1;

Posteriormente verificamos:

SELECT
    h.handle,
    hc.oai_set_id,
    hc.metadata_config_id,
    hc.harvest_status,
    hc.harvest_message
FROM harvested_collection hc
JOIN handle h
    ON h.resource_id = hc.collection_id
WHERE h.handle IN (
    '20.500.00000/LOREM01',
    '20.500.00000/LOREM02',
    '20.500.00000/LOREM03',
    '20.500.00000/LOREM04'
)
ORDER BY h.handle;

Obteniendo nuevamente:

harvest_status  = 0
harvest_message = Ready

Esto no necesariamente solucionaba la causa original, pero permitía que DSpace volviera a iniciar el harvester y mostrara el error que había provocado el bloqueo.

5. El verdadero error: un esquema de metadatos inexistente

Al ejecutar nuevamente el harvesting apareció el problema original:

org.dspace.content.crosswalk.CrosswalkException:
The 'others' schema has not been defined in this DSpace instance.

La traza mostraba que la excepción se producía durante:

CrosswalkMetadataValidator.checkMetadata
DIMIngestionCrosswalk.ingest
OAIHarvester.processRecord
OAIHarvester.runHarvest

El proveedor OAI-PMH estaba entregando registros DIM que contenían metadatos pertenecientes al esquema:

others

pero ese esquema no se encontraba definido en el registro de metadatos del DSpace destino.

Por ejemplo, un proveedor puede entregar mediante DIM campos similares a:

<dim:field
    mdschema="others"
    element="lorem"
    qualifier="ipsum">
    ...
</dim:field>

Si el DSpace destino no conoce el esquema others, la validación del crosswalk falla y el registro no puede ser ingerido.

6. La importancia de los esquemas personalizados

Cuando se utiliza DIM para intercambiar información entre instalaciones DSpace, el destino debe ser compatible con los esquemas y campos de metadatos enviados por el origen.

Por este motivo, antes de realizar una cosecha entre repositorios resulta conveniente comparar sus registros de metadatos.

En el origen podemos identificar los campos pertenecientes a un esquema personalizado mediante una consulta similar a:

SELECT
    ms.short_id AS schema,
    mf.element,
    mf.qualifier
FROM metadatafieldregistry mf
JOIN metadataschemaregistry ms
    ON ms.metadata_schema_id = mf.metadata_schema_id
WHERE ms.short_id = 'others'
ORDER BY mf.element, mf.qualifier;

Posteriormente pueden registrarse en el destino aquellos esquemas y campos necesarios para la ingestión.

No es recomendable limitarse a crear únicamente el esquema. Si el origen utiliza campos others.* que tampoco existen en el destino, la ingestión puede volver a detenerse al validar esos campos.

7. Qualified Dublin Core y DIM

Durante el análisis encontramos además una diferencia relevante entre las colecciones.

Algunas utilizaban:

metadata_config_id = qdc

mientras otras utilizaban:

metadata_config_id = dim

Esta diferencia es importante.

Cuando se utiliza DIM, el intercambio conserva una representación mucho más cercana a los esquemas internos de metadatos de DSpace. Por esta razón, los esquemas personalizados existentes en el origen pueden convertirse directamente en dependencias para el repositorio destino.

Una colección configurada mediante Qualified Dublin Core puede, por lo tanto, comportarse de manera diferente a otra configurada mediante DIM aunque ambas estén recolectando información desde el mismo proveedor OAI-PMH.

8. Otro problema: “Restaurar y reimportar”

Durante las pruebas también utilizamos desde la administración de DSpace la función para restaurar y reimportar completamente una colección.

En este caso apareció un error diferente:

javax.persistence.OptimisticLockException:
Batch update returned unexpected row count from update [0];
actual row count: 0;
expected: 1;

delete from public.metadatavalue
where metadata_value_id=?

La traza mostraba:

SubscriptionDAOImpl.deleteByDspaceObject
SubscribeServiceImpl.deleteByDspaceObject
ItemServiceImpl.rawDelete
ItemServiceImpl.delete
CollectionServiceImpl.removeItem
Harvest.purgeCollection

Esto es importante porque demuestra que el fallo no estaba ocurriendo en la comunicación OAI-PMH.

La restauración completa intenta primero purgar contenido existente de la colección. Durante esa operación, Hibernate intentó eliminar un registro metadatavalue que esperaba encontrar en PostgreSQL, pero la operación afectó cero filas.

También aparecieron advertencias como:

REMOVE event, could not get object for BUNDLE id=<UUID>,
perhaps it has been deleted.

REMOVE event, could not get object for ITEM id=<UUID>,
perhaps it has been deleted.

Esto apuntaba a una inconsistencia durante la eliminación de objetos existentes y debía investigarse separadamente del problema OAI-PMH.

Por este motivo es importante distinguir entre:

Harvest normal
    ↓
Obtención e ingestión de registros

Restaurar y reimportar
    ↓
Purga del contenido existente
    ↓
Nueva ingestión

Un error durante purgeCollection() no significa necesariamente que el proveedor OAI-PMH esté fallando.

9. Harvesting nuevamente operativo

Una vez restablecido el estado y solucionadas las incompatibilidades correspondientes, pudimos ejecutar nuevamente:

/opt/dspace/bin/dspace harvest \
-r \
-e administrador@example.org \
-c 20.500.00000/LOREM03

obteniendo:

The script has started
Running: a harvest cycle on 20.500.00000/LOREM03
Initializing the harvester...
Initialized the harvester successfully
Harvest started...
Harvest complete.
The script has completed

En este punto el harvesting había finalizado correctamente.

Sin embargo, apareció una última diferencia interesante.

10. El proveedor OAI mostraba más registros que la interfaz

El proveedor OAI-PMH indicaba un total de:

Results fetched 1 - 100 of 415

mientras que la interfaz del repositorio destino mostraba solamente:

408 items

Era razonable sospechar que siete registros no habían sido cosechados.

Sin embargo, antes de realizar otra importación comprobamos directamente PostgreSQL.

Primero identificamos el UUID de la colección y posteriormente contamos sus asociaciones en collection2item:

SELECT COUNT(*)
FROM collection2item
WHERE collection_id = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';

Resultado:

 count
-------
 415

Por lo tanto teníamos:

OAI-PMH origen       415
PostgreSQL destino   415
Interfaz destino     408

Los siete registros aparentemente faltantes sí habían sido cosechados.

La diferencia estaba entre la información persistida en PostgreSQL y la capa utilizada por la interfaz para recuperar y presentar los objetos.

11. Discovery y la indexación

DSpace utiliza Discovery/Solr para búsquedas, facetas y diferentes mecanismos de presentación de contenidos.

Por este motivo puede ocurrir que PostgreSQL contenga correctamente un conjunto de objetos mientras el índice todavía no represente el mismo estado.

Ante esta situación corresponde revisar la indexación Discovery.

Por ejemplo:

/opt/dspace/bin/dspace index-discovery -b

Dependiendo de la versión de DSpace y del alcance de la incidencia, también puede ser necesario realizar una reconstrucción completa del índice.

La conclusión importante es que no debemos volver a cosechar automáticamente una colección solo porque la interfaz muestra menos registros que el proveedor OAI-PMH.

Primero debemos consultar la capa de persistencia.

12. Una metodología práctica de diagnóstico

El caso nos permitió establecer una secuencia útil para diagnosticar problemas similares:

PROVEEDOR OAI-PMH
       │
       ▼
Identify / ListSets / ListRecords
       │
       ▼
¿El proveedor responde correctamente?
       │
       ▼
HARVESTED_COLLECTION
       │
       ▼
¿La colección está configurada?
¿harvest_status permite ejecutar?
       │
       ▼
CROSSWALK
       │
       ▼
QDC / DIM
¿Existen schemas y fields requeridos?
       │
       ▼
HARVESTING
       │
       ▼
¿Finaliza correctamente?
       │
       ▼
POSTGRESQL
       │
       ▼
¿Los ítems existen en collection2item?
       │
       ▼
DISCOVERY / SOLR
       │
       ▼
¿El índice contiene los mismos objetos?
       │
       ▼
INTERFAZ

Esta separación es particularmente útil porque evita atribuir todos los problemas al harvesting.

13. Consultas útiles para diagnóstico

Obtener UUID a partir de un Handle

SELECT d.uuid, h.handle
FROM dspaceobject d
JOIN handle h
    ON h.resource_id = d.uuid
WHERE h.handle = '20.500.00000/LOREM01';

Revisar configuración del harvesting

SELECT *
FROM harvested_collection
WHERE collection_id = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';

Revisar estado de varias colecciones

SELECT
    h.handle,
    hc.oai_set_id,
    hc.metadata_config_id,
    hc.harvest_status,
    hc.harvest_message,
    hc.last_harvested
FROM harvested_collection hc
JOIN handle h
    ON h.resource_id = hc.collection_id
ORDER BY h.handle;

Contar ítems reales asociados a una colección

SELECT COUNT(*)
FROM collection2item
WHERE collection_id = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';

Buscar un esquema de metadatos

SELECT *
FROM metadataschemaregistry
WHERE short_id = 'others';

Revisar los campos pertenecientes a un esquema

SELECT
    ms.short_id,
    mf.element,
    mf.qualifier
FROM metadatafieldregistry mf
JOIN metadataschemaregistry ms
    ON ms.metadata_schema_id = mf.metadata_schema_id
WHERE ms.short_id = 'others'
ORDER BY mf.element, mf.qualifier;

Conclusiones

Este caso demuestra que un problema aparentemente simple de harvesting OAI-PMH puede involucrar varias capas independientes de DSpace.

Durante el diagnóstico encontramos cuatro situaciones diferentes:

  1. Colecciones bloqueadas con harvest_status = -1, pese a estar correctamente configuradas para harvesting.
  2. Una incompatibilidad de metadatos, causada por un esquema personalizado existente en el proveedor pero ausente en el repositorio destino.
  3. Una inconsistencia durante purgeCollection(), que afectaba la función de restaurar y reimportar y que era independiente de la comunicación OAI-PMH.
  4. Una diferencia entre PostgreSQL y la interfaz, donde el proveedor exponía 415 registros, PostgreSQL contenía los mismos 415 ítems, pero Discovery mostraba solamente 408.

La principal lección es que debemos tratar el proceso como varias capas independientes:

OAI-PMH → Crosswalk → Harvesting → PostgreSQL → Discovery/Solr → Interfaz

Cuando la interfaz muestra menos ítems de los esperados, no debemos asumir inmediatamente que el harvesting está incompleto. Del mismo modo, cuando DSpace indica que una colección no está preparada para harvesting, conviene verificar el estado almacenado en harvested_collection antes de modificar su configuración.

Revisar cada capa de manera independiente permite llegar mucho más rápido a la causa real y, sobre todo, evita reimportaciones, purgas o modificaciones innecesarias sobre contenidos que pueden encontrarse perfectamente almacenados en la base de datos.