Inventario de shortcuts en OneLake: cómo detectar huérfanos, ciclos y fugas de governance con un notebook de Fabric

Autor

Kilian Baccaro Salinas

Categoría

Data Engineering

Tiempo de lectura

06 min de lectura

Fecha de publicación

13 jul, 2026

Los shortcuts de OneLake son una de las funcionalidades que más me gustan de Microsoft Fabric: te permiten referenciar datos de otro lakehouse, otro workspace o incluso un storage externo (S3, ADLS Gen2, GCS…) sin mover ni duplicar nada. El problema es que son demasiado fáciles de crear, y con el tiempo cualquier tenant medianamente activo acumula:

  • Gran cantidad de shortcuts que nadie recuerda para qué sirven (ruido).
  • Shortcuts que apuntan a un ítem que alguien borró hace meses (huérfanos).
  • Cadenas de shortcuts que apuntan entre sí formando un ciclo (circulares).
  • Shortcuts hacia storage externo en lakehouses sin ninguna etiqueta de sensibilidad ni endorsement (fugas de governance).

Fabric no tiene, hoy por hoy, una vista centralizada que te enseñe esto de un vistazo. Así que construí un notebook que lo hace: fabric-shortcut-inventory. En este artículo repaso cómo está construido, qué decisiones de diseño tomé y cómo lo puedes adaptar a tu propio tenant.

Qué problema resuelve

El notebook escanea uno o varios workspaces, recorre todos los ítems que soportan shortcuts y construye un inventario fila a fila (un shortcut = una fila) con toda la metadata necesaria para responder a tres preguntas de auditoría:

  1. ¿Qué shortcuts existen en el tenant y a qué apuntan?
  2. ¿Qué shortcuts ya no apuntan a nada?
  3. ¿Hay dependencias circulares entre lakehouses?
  4. ¿Qué shortcuts salen del tenant sin que nadie los haya etiquetado o certificado?

El resultado es un informe HTML interactivo (con pestañas y colores por fila) que se renderiza dentro del propio notebook, y opcionalmente una tabla Delta para poder consultarlo con SQL o montar un informe de Power BI encima.

Vista general del informe HTML

Arquitectura del notebook

El flujo tiene ocho pasos bien diferenciados. Los repaso uno a uno porque cada uno resuelve un problema concreto que me fui encontrando al escanear tenants reales.

1. Descubrimiento de workspaces

El notebook soporta dos modos, controlados por el parámetro SCOPE_MODE:

  • "all": escanea todo el tenant (o todos los workspaces a los que tengas acceso, si no eres administrador de Fabric).
  • "list": limita el escaneo a una lista concreta (WORKSPACE_LIST), por nombre o por ID.

La razón de tener ambos modos no es solo comodidad. La detección de huérfanos se evalúa contra el ámbito escaneado: si limitas el escaneo a una lista de workspaces y un shortcut apunta a un lakehouse que vive fuera de esa lista, el notebook lo marcará como huérfano aunque en realidad exista. Para un diagnóstico fiable de huérfanos, hay que escanear con "all".

2. Descubrimiento de ítems

Para cada workspace del ámbito, el notebook lista sus ítems vía la API REST de Fabric. De esa lista me quedo únicamente con los tipos que exponen el endpoint de shortcuts: Lakehouse, KQL Database y MirroredDatabase. Cualquier otro tipo de ítem se omite directamente, porque la API no tiene nada que devolver ahí.

3. Extracción de shortcuts

Aquí es donde entra el endpoint List Shortcuts de la API de Fabric:

GET https://api.fabric.microsoft.com/v1/workspaces/{workspaceId}/items/{itemId}/shortcuts

La respuesta trae, para cada shortcut, su path, su name y un objeto target cuyo type indica si apunta a OneLake o a un storage externo (AmazonS3, AdlsGen2, GoogleCloudStorage, S3Compatible, Dataverse, AzureBlobStorage, OneDriveSharePoint…).

Con un tenant grande, este paso es el que más tarda, así que lo paralelizo con un ThreadPoolExecutor controlado por el parámetro MAX_WORKERS. La API de shortcuts tiene un límite de 200 peticiones por minuto por operación y por usuario, así que si empiezas a ver errores 429 la primera palanca a tocar es bajar MAX_WORKERS, no reintentar más agresivamente.

Un ejemplo simplificado de esta fase:

from concurrent.futures import ThreadPoolExecutor, as_completed
 
def get_shortcuts(workspace_id, item_id):
    uri = f"workspaces/{workspace_id}/items/{item_id}/shortcuts"
    try:
        return client.get(uri).json().get("value", [])
    except Exception as e:
        return {"error": str(e)}
 
rows = []
with ThreadPoolExecutor(max_workers=MAX_WORKERS) as pool:
    futures = {
        pool.submit(get_shortcuts, ws_id, it["id"]): (ws_id, it)
        for ws_id, items in items_by_workspace.items()
        for it in items
        if it["type"] in ("Lakehouse", "KQLDatabase", "MirroredDatabase")
    }
    for future in as_completed(futures):
        ws_id, item = futures[future]
        rows.append((ws_id, item, future.result()))

4. Parseo de destinos

Cada shortcut trae su destino en un formato distinto según el type. Para los internos (OneLake), lo normalizo a workspace_id / item_id / path; para los externos, extraigo lo relevante de cada esquema (bucket y subpath en S3, container y subpath en ADLS Gen2, etc.) y los marco con is_external = True. Esta normalización es la que permite tratar de forma homogénea 8 tipos de destino distintos en las fases siguientes.

5. Validación: huérfanos, circulares y governance

Esta es la parte con más chicha del notebook.

Huérfanos. Un shortcut es huérfano si su target_item_id no existe en el inventario de ítems del ámbito escaneado. Es una comprobación en memoria contra el diccionario de ítems ya descubierto en el paso 2 — no hace falta ninguna llamada adicional a la API.

Circulares. Construyo un grafo dirigido donde cada nodo es un ítem y cada arista es un shortcut que apunta de un ítem a otro. Sobre ese grafo hago una búsqueda en profundidad (DFS) clásica con detección de ciclos usando tres estados por nodo (blanco/gris/negro), que es el algoritmo estándar para esto:

WHITE, GRAY, BLACK = 0, 1, 2
 
def has_cycle(graph):
    state = {node: WHITE for node in graph}
    cyclic_edges = []
 
    def dfs(node, path):
        state[node] = GRAY
        for neighbor in graph.get(node, []):
            if state[neighbor] == GRAY:
                cyclic_edges.append(path + [neighbor])
            elif state[neighbor] == WHITE:
                dfs(neighbor, path + [neighbor])
        state[node] = BLACK
 
    for node in graph:
        if state[node] == WHITE:
            dfs(node, [node])
    return cyclic_edges

Cada arista que participa en un ciclo se marca con is_circular = True y se guarda esa arista concreta (origen -> destino) en circular_path. No es la cadena completa del ciclo en una sola celda: para reconstruirla hay que juntar todas las filas marcadas como circulares, pero es suficiente para ir al Fabric portal y saber qué shortcuts romper.

Governance. Un shortcut se marca con governance_flag cuando es externo (is_external = True) y el ítem que lo contiene no tiene ni etiqueta de sensibilidad ni endorsement (Certified o Promoted). Esto requiere el admin scan de sempy, que a su vez exige permisos de administrador de Fabric; si el notebook se ejecuta sin esos permisos, estas columnas simplemente quedan vacías en lugar de fallar la ejecución completa. Es una degradación intencionada: prefiero un informe parcial a un notebook que se cae por un tema de permisos.

6. Resolución de nombres

Todo lo anterior trabaja con GUIDs, que son estables pero ilegibles. En este paso resuelvo los workspace_id e item_id de destino a nombres legibles, incluso cuando el destino vive fuera del ámbito escaneado (siempre que tengas visibilidad sobre él). El resultado va a la columna target_location_display, mientras que target_location conserva la URI con GUIDs para poder hacer joins o automatizaciones posteriores.

7. El informe HTML

El informe se genera con displayHTML directamente dentro del notebook, sin ninguna dependencia externa (nada de CDNs ni librerías JS de terceros).

Tiene cuatro pestañas —General, Circulares, Orphan y Governance—, cada una con su propio contador, y un código de colores por fila:

  • 🟥 huérfano
  • 🟧 circular
  • 🟨 flag de governance

Esto lo hace cómodo para compartir: puedes exportar la salida del notebook o simplemente enseñar la pestaña a quien tenga que decidir qué limpiar primero.

8. Persistencia opcional en Delta

Si activas SAVE_TO_DELTA, el inventario completo se escribe en una tabla Delta (DELTA_TABLE) en modo overwrite.

La pensé así para ejecuciones programadas: cada ejecución sustituye a la anterior y siempre tienes la foto más reciente. Si prefieres conservar histórico para ver la evolución en el tiempo, basta con cambiar el modo de escritura a append en la última celda.

Autenticación: usuario o service principal

El parámetro AUTH_MODE soporta dos modos:

  • "user": usa el token delegado del usuario que ejecuta el notebook. Cómodo para ejecuciones manuales y para probar el notebook la primera vez.
  • "sp": usa un service principal, con SP_TENANT_ID, SP_CLIENT_ID y el secreto guardado en Azure Key Vault (SP_KEYVAULT + SP_SECRET_NAME). El secreto nunca se escribe en texto plano en el notebook, algo importante si vas a programar la ejecución con un pipeline.

Para ejecuciones programadas, "sp" es la opción recomendada — es la misma lógica que ya sigo en otros notebooks de administración de Fabric: nunca dejar credenciales colgando en el código.

Cómo lo uso

  1. Importo shortcut_inventory.ipynb en un workspace de Fabric.
  2. Ajusto la celda de parámetros según el ámbito que quiera auditar.
  3. Ejecuto todas las celdas. El informe HTML aparece al final de la última.

Para el escaneo completo del tenant necesitas rol de administrador de Fabric; para un escaneo con SCOPE_MODE = "list" basta con rol de Viewer en los workspaces incluidos. El GOVERNANCE_CHECK también requiere permisos de administrador (usa el admin scan de sempy), pero como comentaba antes, si no los tienes el notebook sigue funcionando y simplemente deja esas columnas vacías.

Limitaciones que conviene tener presentes

  • Solo Lakehouse, KQL Database y MirroredDatabase exponen el endpoint de shortcuts hoy; cualquier otro tipo de ítem queda fuera del inventario.
  • La detección de huérfanos depende del ámbito escaneado: con SCOPE_MODE = "list" puedes tener falsos positivos si el destino real vive fuera de la lista.
  • El enriquecimiento de governance depende de permisos de administrador; sin ellos, no hay item_sensitivity_label ni item_endorsement.
  • El guardado en Delta sobrescribe por defecto — si quieres histórico, cambia a append.

Por qué merece la pena tenerlo programado

Lo interesante de este notebook no es solo lanzarlo una vez y limpiar lo que encuentres. Programado semanalmente con AUTH_MODE = "sp" y SAVE_TO_DELTA = True, te da una tabla con la evolución del “ruido” de shortcuts en el tenant: cuántos huérfanos nuevos aparecen cada semana, si los ciclos crecen, si el número de shortcuts externos sin etiquetar sube o baja. Es la misma lógica de observabilidad que aplicaría a cualquier otro activo de datos, aplicada a algo que Fabric no te da de fábrica.

El notebook completo, con el esquema de salida detallado columna a columna, está en el repositorio: github.com/kilianbs/fabric-shortcut-inventory. Si lo pruebas en tu tenant y te encuentras con algún tipo de destino que no esté contemplado, iría genial que lo reportaras en el repo.