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.
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.
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:
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
SELECT d.uuid, h.handle
FROM dspaceobject d
JOIN handle h
ON h.resource_id = d.uuid
WHERE h.handle = '20.500.00000/LOREM01';
SELECT *
FROM harvested_collection
WHERE collection_id = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
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;
SELECT COUNT(*)
FROM collection2item
WHERE collection_id = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
SELECT *
FROM metadataschemaregistry
WHERE short_id = 'others';
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;
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:
harvest_status = -1, pese a estar correctamente configuradas para harvesting.purgeCollection(), que afectaba la función de restaurar y reimportar y que era independiente de la comunicación OAI-PMH.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.