Segundo plano del firewall de privacidad de datos

Nota:

Los niveles de privacidad no están disponibles actualmente en los flujos de datos de Power Platform, pero el equipo del producto está trabajando para habilitar esta funcionalidad.

Si ha usado Power Query durante cualquier período de tiempo, es probable que lo haya experimentado. Ahí está, haciendo una consulta, cuando de repente aparece un error que ninguna búsqueda en línea, ajuste de la consulta o aporreo del teclado puede remediar. Un error como:

Formula.Firewall: Query 'Query1' (step 'Source') references other queries or steps, so it may not directly access a data source. Please rebuild this data combination.

O quizás:

Formula.Firewall: Query 'Query1' (step 'Source') is accessing data sources that have privacy levels which cannot be used together. Please rebuild this data combination.

Estos Formula.Firewall errores son el resultado del firewall de privacidad de datos de Power Query (también conocido como firewall) que, en ocasiones, puede parecer que existe únicamente para frustrar a los analistas de datos en todo el mundo. Aunque no lo crea, el firewall tiene una función importante. En este artículo profundizaremos en su funcionamiento. Armado con una mayor comprensión, es de esperar que pueda diagnosticar y solucionar mejor los errores de firewall en el futuro.

¿Qué es?

El propósito del firewall de privacidad de datos es simple: existe para evitar que Power Query filtre involuntariamente datos entre orígenes.

¿Por qué es necesario? Es decir, ciertamente podría crear algún M que pasara un valor SQL a una fuente de OData. Pero esto sería una fuga de datos intencionada. El autor del mashup sabría (o al menos debería saber) que lo hacían. ¿Por qué entonces la necesidad de protección contra la fuga involuntaria de datos?

¿La respuesta? Plegado.

¿Plegado?

El plegado es un término que hace referencia a la conversión de expresiones en M (como filtros, cambios de nombre, combinaciones, etc.) en operaciones en un origen de datos sin procesar (como SQL, OData, etc.). Gran parte de la eficacia de Power Query proviene del hecho de que PQ puede convertir las operaciones que un usuario realiza a través de su interfaz de usuario en complejos lenguajes SQL u otros lenguajes de orígenes de datos back-end, sin que el usuario tenga que conocer dichos lenguajes. Los usuarios obtienen las ventajas de rendimiento de las operaciones de orígenes de datos nativas, con la facilidad de uso de una interfaz de usuario en la que todos los orígenes de datos pueden transformarse utilizando un conjunto común de comandos.

Como parte del plegado, PQ a veces puede determinar que la forma más eficiente de ejecutar un mashup dado es tomar datos de un origen y pasarlos a otro. Por ejemplo, si está uniendo un pequeño archivo CSV a una enorme tabla SQL, probablemente no quiera que PQ lea el archivo CSV, lea toda la tabla SQL y luego los una en su ordenador local. Lo más probable es que quiera que PQ incluya los datos CSV en una instrucción SQL y pida a la base de datos SQL que realice la combinación.

Así es como puede producirse una fuga de datos involuntaria.

Imagínese que estuviera combinando datos de SQL que incluyeran los números de la Seguridad Social de los empleados con los resultados de una fuente de OData externa y, de repente, descubriera que los números de la Seguridad Social de SQL se estaban enviando al servicio de OData. Malas noticias, ¿verdad?

Este es el tipo de escenario que el firewall pretende evitar.

¿Cómo funciona?

El firewall existe para evitar que los datos de un origen se envíen involuntariamente a otro. Muy sencillo.

¿Y cómo cumple esta misión?

Lo hace dividiendo sus consultas M en algo llamado particiones y luego aplicando la siguiente regla:

  • Una partición puede acceder a orígenes de datos compatibles o hacer referencia a otras particiones, pero no a ambos.

Sencillo... pero confuso. ¿Qué es una partición? ¿Qué hace que dos orígenes de datos sean "compatibles"? ¿Y por qué debería importarle al firewall si una partición quiere acceder a un origen de datos y hacer referencia a una partición?

Desglosemos la regla anterior una por una.

¿Qué es una partición?

En su nivel más básico, una partición es simplemente una colección de uno o más pasos de consulta. La partición más granular posible (al menos en la implementación actual) es un único paso. A veces, las particiones más grandes pueden abarcar varias consultas. (Esto se explica más adelante).

Si no está familiarizado con los pasos, puede verlos a la derecha de la ventana del Editor de Power Query después de seleccionar una consulta, en el panel Pasos aplicados. Los pasos realizan un seguimiento de todo lo que ha hecho para transformar sus datos en su forma final.

Particiones que hacen referencia a otras particiones

Cuando se evalúa una consulta con el firewall activado, este divide la consulta y todas sus dependencias en particiones (es decir, en grupos de pasos). Cada vez que una partición hace referencia a algo en otra partición, el firewall sustituye la referencia por una llamada a una función especial denominada Value.Firewall. En otras palabras, el firewall no permite que las particiones accedan unas a otras directamente. Todas las referencias se modifican para pasar por el firewall. Piense en el firewall como un guardián. Una partición que hace referencia a otra partición debe obtener el permiso del firewall para hacerlo y este controla si los datos a los que se hace referencia se permitirán o no en la partición.

Todo esto puede parecer bastante abstracto, así que veamos un ejemplo.

Supongamos que tiene una consulta llamada Empleados, que extrae algunos datos de una base de datos SQL. Supongamos que también tiene otra consulta (EmployeesReference) que simplemente hace referencia a Empleados.

shared Employees = let
    Source = Sql.Database(…),
    EmployeesTable = …
in
    EmployeesTable;

shared EmployeesReference = let
    Source = Employees
in
    Source;

Estas consultas acabarán divididas en dos particiones: una para la consulta Empleados y otra para la consulta EmployeesReference (que hará referencia a la partición Empleados). Cuando se evalúen con el firewall activado, estas consultas se reescribirán de la siguiente manera:

shared Employees = let
    Source = Sql.Database(…),
    EmployeesTable = …
in
    EmployeesTable;

shared EmployeesReference = let
    Source = Value.Firewall("Section1/Employees")
in
    Source;

Observe que la simple referencia a la consulta Empleados se ha sustituido por una llamada a Value.Firewall, a la que se proporciona el nombre completo de la consulta Empleados.

Cuando se evalúa EmployeesReference, la llamada a Value.Firewall("Section1/Employees") es interceptada por el firewall, que ahora tiene la oportunidad de controlar si (y cómo) los datos solicitados fluyen a la partición EmployeesReference. Puede hacer varias cosas: denegar la solicitud, almacenar en búfer los datos solicitados (lo que impide que se produzcan más plegados a su origen de datos original), etcétera.

Así es como el firewall mantiene el control sobre los datos que fluyen entre las particiones.

Particiones que acceden directamente a orígenes de datos

Supongamos que define una consulta Query1 con un paso (tenga en cuenta que esta consulta de un solo paso corresponde a una partición del firewall) y que este único paso accede a dos orígenes de datos: una tabla de base de datos SQL y un archivo CSV. ¿Cómo se las arregla el firewall con esto, ya que no hay referencia de partición y, por lo tanto, no hay llamada a Value.Firewall para que la intercepte? Repasemos la regla anterior:

  • Una partición puede acceder a orígenes de datos compatibles o hacer referencia a otras particiones, pero no a ambos.

Para que su consulta de una sola partición pero dos orígenes de datos pueda ejecutarse, sus dos orígenes de datos deben ser "compatibles". En otras palabras, los datos deben poder compartirse entre ellas de manera bidireccional. Esto significa que los niveles de privacidad de ambos orígenes deben ser públicos, o ser ambos ser organizativos, ya que son las únicas dos combinaciones que permiten compartir en ambas direcciones. Si ambos orígenes vienen marcados como privados, o uno está marcado como público y otro está marcado como organizativo, o si se marcan con alguna otra combinación de niveles de privacidad, no se permitirá el uso compartido bidireccional y, por tanto, no es seguro que ambos se evalúen en la misma partición. Hacerlo significaría que podría producirse una fuga de datos insegura (debido al plegado) y que el firewall no tendría forma de evitarlo.

¿Qué ocurre si intenta acceder a orígenes de datos incompatibles en la misma partición?

Formula.Firewall: Query 'Query1' (step 'Source') is accessing data sources that have privacy levels which cannot be used together. Please rebuild this data combination.

Esperemos que ahora entienda mejor uno de los mensajes de error que aparecen al principio de este artículo.

Tenga en cuenta que este requisito de compatibilidad solo se aplica dentro de una partición determinada. Si una partición hace referencia a otras particiones, los orígenes de datos de las particiones a las que se hace referencia no tienen que ser compatibles entre sí. Esto se debe a que el firewall puede almacenar los datos en el búfer, lo que evitará cualquier otro plegado contra el origen de datos original. Los datos se cargarán en memoria y se tratarán como si no vinieran de ninguna parte.

¿Por qué no hacer las dos cosas?

Supongamos que define una consulta con un paso (que corresponderá de nuevo a una partición) que accede a otras dos consultas (es decir, a otras dos particiones). ¿Y si en el mismo paso quisiera también acceder directamente a una base de datos SQL? ¿Por qué una partición no puede hacer referencia a otras particiones y acceder directamente a orígenes de datos compatibles?

Como ha visto antes, cuando una partición hace referencia a otra partición, el firewall actúa como guardián de todos los datos que fluyen hacia la partición. Para ello, debe ser capaz de controlar qué datos están permitidos. Si hay orígenes de datos a los que se accede dentro de la partición y datos que fluyen desde otras particiones, pierde su capacidad de ser el guardián, ya que los datos que fluyen podrían filtrarse a uno de los orígenes de datos a los que se accede internamente sin que el firewall lo sepa. Así, el firewall impide que una partición que accede a otras particiones pueda acceder directamente a cualquier origen de datos.

Entonces, ¿qué ocurre si una partición intenta hacer referencia a otras particiones y también acceder directamente a orígenes de datos?

Formula.Firewall: Query 'Query1' (step 'Source') references other queries or steps, so it may not directly access a data source. Please rebuild this data combination.

Ahora espero que entienda mejor el otro mensaje de error que aparece al principio de este artículo.

Particiones en profundidad

Como probablemente puede adivinar de la información anterior, la forma en que las consultas se particionan termina siendo increíblemente importante. Si tiene algunos pasos que hacen referencia a otras consultas y otros pasos que acceden a orígenes de datos, es de esperar que ahora reconozca que dibujar los límites de la partición en ciertos lugares causará errores del firewall, mientras que dibujarlos en otros lugares permitirá que su consulta se ejecute sin problemas.

Entonces, ¿cómo se particionan exactamente las consultas?

Esta sección es probablemente la más importante para entender por qué está viendo errores del firewall para entender cómo resolverlos (cuando sea posible).

He aquí un resumen general de la lógica del particionado.

  • Particionado inicial
    • Crea una partición para cada paso en cada consulta
  • Fase estática
    • Esta fase no depende de los resultados de la evaluación. En su lugar, se basa en cómo están estructuradas las consultas.
      • Recorte de parámetros
        • Recorta las particiones con parámetros, es decir, cualquiera que:
          • No haga referencia a otras particiones
          • No contenga ninguna invocación a funciones
          • No sea cíclica (es decir, que no haga referencia a sí misma)
        • Tenga en cuenta que "eliminar" una partición la incluye efectivamente en cualquier otra partición que haga referencia a ella.
        • Recortar particiones de parámetros permite que las referencias a parámetros utilizadas dentro de llamadas a funciones de orígenes de datos (por ejemplo, Web.Contents(myUrl)) funcionen, en lugar de arrojar errores de "la partición no puede hacer referencia a orígenes de datos y otros pasos".
      • Agrupación (estática)
        • Las particiones se combinan en orden de dependencia de inferior a superior. En las particiones combinadas resultantes, las siguientes serán independientes:
          • Particiones en diferentes consultas
          • Particiones que no hacen referencia a otras particiones (y, por tanto, se les permite acceder a un origen de datos).
          • Particiones que hacen referencia a otras particiones (y, por tanto, no se les permite acceder a un origen de datos).
  • Fase dinámica
    • Esta fase depende de los resultados de la evaluación, incluida la información sobre los orígenes de datos a los que acceden las distintas particiones.
    • Recorte
      • Recorta las particiones que cumplen todos los requisitos siguientes:
        • No accede a ningún origen de datos
        • No hace referencia a ninguna partición que acceda a orígenes de datos
        • No es cíclica
    • Agrupación (dinámica)
      • Ahora que se han recortado las particiones innecesarias, intente crear particiones de origen que sean lo más grandes posible. Para ello, se combinan las particiones con las mismas reglas descritas en la fase de agrupación estática anterior.

¿Qué significa todo esto?

Veamos un ejemplo para ilustrar cómo funciona la compleja lógica descrita anteriormente.

Este es un escenario de ejemplo. Se trata de una combinación bastante sencilla de un archivo de texto (Contactos) con una base de datos SQL (Empleados), en la que el servidor SQL es un parámetro (DbServer).

Las tres consultas

A continuación se muestra el código M de las tres consultas utilizadas en este ejemplo.

shared DbServer = "montegoref6" meta [IsParameterQuery=true, Type="Text", IsParameterQueryRequired=true];
shared Contacts = let

    Source = Csv.Document(File.Contents("C:\contacts.txt"),[Delimiter="   ", Columns=15, Encoding=1252, QuoteStyle=QuoteStyle.None]),

    #"Promoted Headers" = Table.PromoteHeaders(Source, [PromoteAllScalars=true]),

    #"Changed Type" = Table.TransformColumnTypes(#"Promoted Headers",{{"ContactID", Int64.Type}, {"NameStyle", type logical}, {"Title", type text}, {"FirstName", type text}, {"MiddleName", type text}, {"LastName", type text}, {"Suffix", type text}, {"EmailAddress", type text}, {"EmailPromotion", Int64.Type}, {"Phone", type text}, {"PasswordHash", type text}, {"PasswordSalt", type text}, {"AdditionalContactInfo", type text}, {"rowguid", type text}, {"ModifiedDate", type datetime}})

in

    #"Changed Type";
shared Employees = let

    Source = Sql.Databases(DbServer),

    AdventureWorks = Source{[Name="AdventureWorks"]}[Data],

    HumanResources_Employee = AdventureWorks{[Schema="HumanResources",Item="Employee"]}[Data],

    #"Removed Columns" = Table.RemoveColumns(HumanResources_Employee,{"HumanResources.Employee(EmployeeID)", "HumanResources.Employee(ManagerID)", "HumanResources.EmployeeAddress", "HumanResources.EmployeeDepartmentHistory", "HumanResources.EmployeePayHistory", "HumanResources.JobCandidate", "Person.Contact", "Purchasing.PurchaseOrderHeader", "Sales.SalesPerson"}),

    #"Merged Queries" = Table.NestedJoin(#"Removed Columns",{"ContactID"},Contacts,{"ContactID"},"Contacts",JoinKind.LeftOuter),

    #"Expanded Contacts" = Table.ExpandTableColumn(#"Merged Queries", "Contacts", {"EmailAddress"}, {"EmailAddress"})

in

    #"Expanded Contacts";

Esta es una vista general, que muestra las dependencias.

Cuadro de diálogo Consulta de dependencias.

Hagamos una partición

Acerquémonos un poco más e incluyamos los pasos en la imagen y empecemos a recorrer la lógica de particionado. Este es un diagrama de las tres consultas, que muestra las particiones iniciales del firewall en verde. Observe que cada paso comienza en su propia partición.

Particiones iniciales del firewall.

A continuación, recortamos las particiones de parámetros. Así, DbServer se incluye implícitamente en la partición de origen.

Particiones recortadas del firewall.

Ahora realizamos la agrupación estática. Esto mantiene la separación entre particiones en consultas separadas (observe, por ejemplo, que los dos últimos pasos de Empleados no se agrupan con los pasos de Contactos) y entre particiones que hacen referencia a otras particiones (como los dos últimos pasos de Empleados) y las que no (como los tres primeros pasos de Empleados).

Particiones del firewall posteriores a la agrupación estática.

Ahora entramos en la fase dinámica. En esta fase, se evalúan las particiones estáticas anteriores. Se recortan las particiones que no acceden a ningún origen de datos. A continuación, las particiones se agrupan para crear particiones de orígenes que sean lo más grandes posible. Sin embargo, en este escenario de ejemplo, todas las particiones restantes acceden a orígenes de datos y no hay ninguna agrupación adicional que se pueda hacer. Por lo tanto, las particiones de nuestro ejemplo no cambiarán durante esta fase.

Imaginemos lo siguiente

Para ilustrarlo, veamos qué pasaría si la consulta de Contactos, en lugar de venir de un fichero de texto, estuviera codificada en M (quizás a través del cuadro de diálogo Introducir datos).

En este caso, la consulta Contactos no accedería a ningún origen de datos. Por lo tanto, se recortaría durante la primera parte de la fase dinámica.

Partición del firewall posterior al recorte de fase dinámica.

Con la partición Contactos eliminada, los dos últimos pasos de Empleados ya no harían referencia a ninguna partición excepto a la que contiene los tres primeros pasos de Empleados. Así, las dos particiones se agruparían.

La partición resultante se parecería a esta.

Particiones finales del firewall.

Ejemplo: Transferir datos de un origen de datos a otro

Bien, basta de explicaciones abstractas. Veamos un escenario común en el que es probable que encuentre un error de firewall y los pasos para resolverlo.

Imagine que quiere buscar el nombre de una empresa en el servicio OData de Northwind y, a continuación, utilizar el nombre de la empresa para realizar una búsqueda en Bing.

En primer lugar, cree una consulta de Empresa para recuperar el nombre de la empresa.

let
    Source = OData.Feed("https://services.odata.org/V4/Northwind/Northwind.svc/", null, [Implementation="2.0"]),
    Customers_table = Source{[Name="Customers",Signature="table"]}[Data],
    CHOPS = Customers_table{[CustomerID="CHOPS"]}[CompanyName]
in
    CHOPS

A continuación, cree una consulta de Buscar que haga referencia a Empresa y transfiérala a Bing.

let
    Source = Text.FromBinary(Web.Contents("https://www.bing.com/search?q=" & Company))
in
    Source

En este punto se encuentra con problemas. Al evaluar la Buscar se genera un error de firewall.

Formula.Firewall: Query 'Search' (step 'Source') references other queries or steps, so it may not directly access a data source. Please rebuild this data combination.

Esto se debe a que el paso Origen de Buscar hace referencia a un origen de datos (bing.com) y también hace referencia a otra consulta/partición (Empresa). Está infringiendo la regla mencionada anteriormente ("una partición puede acceder a orígenes de datos compatibles o hacer referencia a otras particiones, pero no a ambos").

¿Qué debe hacer? Una opción es deshabilitar el firewall por completo (a través de la opción de Privacidad etiquetada como Ignorar los niveles de privacidad y mejorar el rendimiento potencialmente). Pero, ¿qué pasa si desea dejar el firewall habilitado?

Para resolver el error sin deshabilitar el firewall, puede combinar Empresa y Buscar en una sola consulta, de la siguiente manera:

let
    Source = OData.Feed("https://services.odata.org/V4/Northwind/Northwind.svc/", null, [Implementation="2.0"]),
    Customers_table = Source{[Name="Customers",Signature="table"]}[Data],
    CHOPS = Customers_table{[CustomerID="CHOPS"]}[CompanyName],
    Search = Text.FromBinary(Web.Contents("https://www.bing.com/search?q=" & CHOPS))
in
    Search

Ahora todo ocurre dentro de una única partición. Suponiendo que los niveles de privacidad de los dos orígenes de datos sean compatibles, el firewall debería estar satisfecho y ya no aparecerá el error.

Esto es todo

Aunque se podría decir mucho más sobre este tema, este artículo introductorio ya es lo suficientemente largo. Esperemos que le haya proporcionado una mejor comprensión del firewall que y le ayude a entender y solucionar los errores del firewall cuando se encuentre con ellos en el futuro.