Compartir a través de


Indexación de Azure Data Lake Storage (versión preliminar)

El servicio DICOM® carga automáticamente archivos DICOM en Azure Data Lake Storage (ADLS) al usar STOW-RS. De este modo, los usuarios pueden consultar sus datos mediante las API DICOMweb™, como WADO-RS o las API de Azure Blob/Data Lake. Sin embargo, con la indexación de almacenamiento, el servicio DICOM indexa automáticamente los archivos DICOM después de cargarlos directamente en el sistema de archivos de ADLS Gen 2. Tanto si los archivos se cargaron mediante STOW-RS, un SDK de Blob de Azure o incluso AzCopy, se puede acceder a ellos mediante las API de DICOMweb™ o ADLS Gen 2.

Prerrequisitos

Configuración de la indexación de almacenamiento

El servicio DICOM indexa un sistema de archivos de ADLS Gen 2 mediante la reacción a eventos de almacenamiento de Blob o Data Lake. Estos eventos deben leerse desde una Azure Storage Queue en la cuenta de almacenamiento de Azure que contiene el sistema de archivos. Una vez en la cola, el servicio DICOM procesa de forma asincrónica cada evento y actualiza el índice en consecuencia.

Crear el destino para eventos de almacenamiento

En primer lugar, cree una cola de almacenamiento en la misma cuenta de Azure Storage conectada al servicio DICOM. El servicio de DICOM también necesita acceso a la cola; debe ser capaz de quitar de la cola y poner en cola mensajes, incluidos los mensajes para errores y tareas complejas desglosadas. Por lo tanto, asegúrese de que la misma identidad administrada que utiliza el servicio DICOM, ya sea asignada por el usuario o por el sistema, tenga asignado el rol Colaborador de datos de cola de almacenamiento.

Publicar eventos de almacenamiento en la cola

Con la cola de almacenamiento en su lugar, los eventos deben publicarse desde la cuenta de almacenamiento en un tema del sistema de Azure Event Grid y enrutarse a la cola mediante una suscripción de Azure Event Grid. Antes de crear la suscripción al evento, asegúrese de conceder el rol Emisor de mensajes de datos de cola de almacenamiento a la suscripción de eventos; la suscripción de eventos necesita permisos para poner en cola los mensajes. La suscripción de eventos puede usar una identidad administrada asignada por el usuario o asignada por el sistema desde el tema del sistema para autenticar sus operaciones.

Nota:

De forma predeterminada, las suscripciones de eventos envían todos los tipos de eventos suscritos a su salida designada. Sin embargo, aunque el servicio DICOM controla correctamente cualquier mensaje, solo puede procesar correctamente los que cumplan los criterios siguientes:

  • El mensaje debe ser un CloudEvent base64.
  • El tipo de evento debe ser uno de los siguientes tipos de eventos:
  • Microsoft.Storage.BlobCreated
  • Microsoft.Storage.BlobDeleted
  • El sistema de archivos debe ser el mismo configurado para el servicio DICOM.
  • La ruta de acceso del archivo debe estar dentro AHDS/{workspace-name}/dicom/{dicom-service-name}[/{partition-name}]
  • El archivo debe ser un archivo DICOM tal como se define en la parte 10 del estándar DICOM.
  • La operación no se puede realizar por el propio servicio de DICOM

La suscripción de eventos se puede configurar para filtrar los datos irrelevantes para evitar el procesamiento y la facturación innecesarios. Asegúrese de configurar el filtro de forma que:

  • El asunto debe comenzar por /blobServices/default/containers/{file-system-name}/blobs/AHDS/{workspace-name}/dicom/{dicom-service-name}/
  • Opcionalmente, el asunto termina con .dcm
  • En filtros avanzados, la clave data.clientRequestId no comienza por tag:{workspace-name}-{dicom-service-name}.dicom.azurehealthcareapis.com,

Habilitación de la indexación de almacenamiento

Una vez configurada la suscripción de Event Grid, el servicio DICOM debe saber desde dónde leer los eventos de almacenamiento. Aunque en versión preliminar, la indexación de almacenamiento solo se puede configurar mediante una plantilla de Azure Resource Manager (ARM) mediante la versión 2025-04-01-preview, que introdujo una nueva propiedad denominada storageConfiguration.storageIndexingConfiguration.storageEventQueueName. Actualmente no está disponible para configurar a través de Azure Portal.

La plantilla de ARM de ejemplo siguiente se puede implementar mediante la CLI de Azure. Incluye todos los recursos necesarios para un servicio DICOM:

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "workspaceName": {
      "type": "String"
    },
    "dicomServiceName": {
      "type": "String"
    },
    "enableDataPartitions": {
      "defaultValue": false,
      "type": "bool"
    },
    "storageAccountName": {
      "type": "String"
    },
    "storageAccountSku": {
      "defaultValue": "Standard_LRS",
      "type": "String"
    },
    "fileSystemName": {
      "type": "String"
    },
    "storageEventQueueName": {
      "defaultValue": "storage-events",
      "type": "String"
    },
    "systemTopicName": {
      "type": "String"
    },
    "eventSubscriptionName": {
      "defaultValue": "dicom-storage-events",
      "type": "String"
    }
  },
  "variables": {
    "storageBlobDataContributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]",
    "storageQueueDataContributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '974c5e8b-45b9-4653-ba55-5f855dd0fb88')]",
    "storageQueueDataMessageSender": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c6a89b2d-59bc-44d0-9896-0f6e12d7b80a')]",
    "dicomIdentityName": "[concat(parameters('storageAccountName'), '-', parameters('storageEventQueueName'))]"
  },
  "resources": [
    {
      "type": "Microsoft.ManagedIdentity/userAssignedIdentities",
      "apiVersion": "2023-01-31",
      "name": "[variables('dicomIdentityName')]",
      "location": "[resourceGroup().location]"
    },
    {
      "type": "Microsoft.ManagedIdentity/userAssignedIdentities",
      "apiVersion": "2023-01-31",
      "name": "[parameters('systemTopicName')]",
      "location": "[resourceGroup().location]"
    },
    {
      "type": "Microsoft.Storage/storageAccounts",
      "apiVersion": "2022-05-01",
      "name": "[parameters('storageAccountName')]",
      "location": "[resourceGroup().location]",
      "sku": {
        "name": "[parameters('storageAccountSku')]"
      },
      "kind": "StorageV2",
      "properties": {
        "isHnsEnabled": true,
        "accessTier": "Hot",
        "supportsHttpsTrafficOnly": true,
        "minimumTlsVersion": "TLS1_2",
        "defaultToOAuthAuthentication": true,
        "allowBlobPublicAccess": false,
        "allowSharedKeyAccess": false,
        "encryption": {
          "keySource": "Microsoft.Storage",
          "requireInfrastructureEncryption": true,
          "services": {
            "blob": {
              "enabled": true
            },
            "queue": {
              "enabled": true
            }
          }
        }
      }
    },
    {
      "type": "Microsoft.Storage/storageAccounts/blobServices/containers",
      "apiVersion": "2022-05-01",
      "name": "[format('{0}/default/{1}', parameters('storageAccountName'), parameters('fileSystemName'))]",
      "dependsOn": [
        "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]"
      ]
    },
    {
      "type": "Microsoft.Storage/storageAccounts/queueServices/queues",
      "apiVersion": "2024-01-01",
      "name": "[format('{0}/default/{1}', parameters('storageAccountName'), parameters('storageEventQueueName'))]",
      "dependsOn": [
          "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]"
      ]
    },
    {
      "type": "Microsoft.Storage/storageAccounts/queueServices/queues",
      "apiVersion": "2024-01-01",
      "name": "[format('{0}/default/{1}-poison', parameters('storageAccountName'), parameters('storageEventQueueName'))]",
      "dependsOn": [
          "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]"
      ]
    },
    {
      "type": "Microsoft.Authorization/roleAssignments",
      "apiVersion": "2021-04-01-preview",
      "name": "[guid(resourceGroup().id, parameters('workspaceName'), parameters('dicomServiceName'))]",
      "location": "[resourceGroup().location]",
      "dependsOn": [
        "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]",
        "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', variables('dicomIdentityName'))]"
      ],
      "properties": {
        "roleDefinitionId": "[variables('storageBlobDataContributor')]",
        "principalId": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', variables('dicomIdentityName'))).principalId]",
        "principalType": "ServicePrincipal"
      },
      "scope": "[concat('Microsoft.Storage/storageAccounts', '/', parameters('storageAccountName'))]"
    },
    {
      "type": "Microsoft.Authorization/roleAssignments",
      "apiVersion": "2021-04-01-preview",
      "name": "[guid(resourceGroup().id, parameters('workspaceName'), parameters('dicomServiceName'), parameters('storageEventQueueName'))]",
      "location": "[resourceGroup().location]",
      "dependsOn": [
        "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]",
        "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', variables('dicomIdentityName'))]"
      ],
      "properties": {
        "roleDefinitionId": "[variables('storageQueueDataContributor')]",
        "principalId": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', variables('dicomIdentityName'))).principalId]",
        "principalType": "ServicePrincipal"
      },
      "scope": "[concat('Microsoft.Storage/storageAccounts', '/', parameters('storageAccountName'))]"
    },
    {
      "type": "Microsoft.Authorization/roleAssignments",
      "apiVersion": "2021-04-01-preview",
      "name": "[guid(resourceGroup().id, parameters('systemTopicName'), parameters('storageEventQueueName'))]",
      "location": "[resourceGroup().location]",
      "dependsOn": [
        "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]",
        "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('systemTopicName'))]"
      ],
      "properties": {
        "roleDefinitionId": "[variables('storageQueueDataMessageSender')]",
        "principalId": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('systemTopicName'))).principalId]",
        "principalType": "ServicePrincipal"
      },
      "scope": "[concat('Microsoft.Storage/storageAccounts', '/', parameters('storageAccountName'))]"
    },
    {
      "type": "Microsoft.EventGrid/systemTopics",
      "apiVersion": "2025-02-15",
      "name": "[parameters('systemTopicName')]",
      "location": "[resourceGroup().location]",
      "identity": {
        "type": "userAssigned",
        "userAssignedIdentities": {
          "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities/', parameters('systemTopicName'))]": {}
        }
      },
      "dependsOn": [
        "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]",
        "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('systemTopicName'))]"
      ],
      "properties": {
          "source": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]",
          "topicType": "Microsoft.Storage.StorageAccounts"
      }
    },
    {
        "type": "Microsoft.EventGrid/systemTopics/eventSubscriptions",
        "apiVersion": "2025-02-15",
        "name": "[concat(parameters('systemTopicName'), '/', parameters('eventSubscriptionName'))]",
        "dependsOn": [
            "[resourceId('Microsoft.EventGrid/systemTopics', parameters('systemTopicName'))]"
        ],
        "properties": {
            "deliveryWithResourceIdentity": {
                "identity": {
                    "type": "UserAssigned",
                    "userAssignedIdentity": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('systemTopicName'))]"
                },
                "destination": {
                    "properties": {
                        "resourceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]",
                        "queueName": "[parameters('storageEventQueueName')]",
                        "queueMessageTimeToLiveInSeconds": 604800
                    },
                    "endpointType": "StorageQueue"
                }
            },
            "filter": {
                "subjectBeginsWith": "[format('/blobServices/default/containers/{0}/blobs/AHDS/{1}/dicom/{2}/', parameters('fileSystemName'), parameters('workspaceName'), parameters('dicomServiceName'))]",
                "subjectEndsWith": ".dcm",
                "includedEventTypes": [
                    "Microsoft.Storage.BlobCreated",
                    "Microsoft.Storage.BlobDeleted"
                ],
                "isSubjectCaseSensitive": true,
                "enableAdvancedFilteringOnArrays": true,
                "advancedFilters": [
                    {
                        "values": [
                          "[format('tag:{0}-{1}.dicom.azurehealthcareapis.com,', parameters('workspaceName'), parameters('dicomServiceName'))]"
                        ],
                        "operatorType": "StringNotBeginsWith",
                        "key": "data.clientRequestId"
                    }
                ]
            },
            "labels": [],
            "eventDeliverySchema": "CloudEventSchemaV1_0",
            "retryPolicy": {
                "maxDeliveryAttempts": 30,
                "eventTimeToLiveInMinutes": 1440
            }
        }
    },
    {
      "type": "Microsoft.HealthcareApis/workspaces",
      "name": "[parameters('workspaceName')]",
      "apiVersion": "2025-04-01-preview",
      "location": "[resourceGroup().location]"
    },
    {
      "type": "Microsoft.HealthcareApis/workspaces/dicomservices",
      "apiVersion": "2025-04-01-preview",
      "name": "[concat(parameters('workspaceName'), '/', parameters('dicomServiceName'))]",
      "location": "[resourceGroup().location]",
      "dependsOn": [
        "[resourceId('Microsoft.HealthcareApis/workspaces', parameters('workspaceName'))]",
        "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', variables('dicomIdentityName'))]",
        "[resourceId('Microsoft.EventGrid/systemTopics/eventSubscriptions', parameters('systemTopicName'), parameters('eventSubscriptionName'))]"
      ],
      "identity": {
        "type": "userAssigned",
        "userAssignedIdentities": {
          "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('dicomIdentityName'))]": {}
        }
      },
      "properties": {
        "storageConfiguration": {
          "storageResourceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]",
          "fileSystemName": "[parameters('fileSystemName')]",
          "storageIndexingConfiguration": {
            "storageEventQueueName": "[parameters('storageEventQueueName')]"
          }
        },
        "enableDataPartitions": "[parameters('enableDataPartitions')]"
      }
    }
  ]
}

Diagnóstico de problemas

Captura de pantalla de Azure Portal que muestra una consulta del lenguaje de consulta kusto (KQL) para la tabla AHDSDicomAuditLogs. La consulta de ejemplo filtra todos los registros en los que OperationName es el almacenamiento de índices de cadena. Una tabla de los resultados de la consulta se encuentra debajo.

Si se produce un error al procesar un evento, el evento problemático se encola en una "cola de mensajes dudosos" denominada {queue-name}-poison en la misma cuenta de almacenamiento. Los detalles sobre cada evento procesado se pueden encontrar en las tablas AHDSDicomAuditLogs y AHDSDicomDiagnosticLogs filtrando todos los registros donde OperationName = 'index-storage'. Los registros de auditoría solo registran cuando se inició y completó la operación, mientras que la tabla de diagnóstico proporciona detalles sobre cada operación, incluidos los errores, si los hubiera. Las operaciones se pueden correlacionar entre las tablas mediante CorrelationId.

Los errores se dividen en dos tipos: User y Server. Los errores de usuario incluyen cualquier problema al conectarse a la cuenta de almacenamiento o con el propio archivo DICOM, mientras que los errores del servidor incluyen cualquier error inesperado que impida el procesamiento. A diferencia de los errores del servidor, el servicio DICOM no reintenta los errores de usuario.

Pasos siguientes