Partilhar via


Nos bastidores do Data Privacy Firewall

Nota

Os níveis de privacidade não estão atualmente disponíveis nos fluxos de dados da Power Platform, mas a equipe do produto está trabalhando para habilitar essa funcionalidade.

Se já utilizou o Power Query durante algum tempo, é provável que já o tenha experimentado. Lá está você, consultando, quando de repente recebe um erro que nenhuma quantidade de pesquisa on-line, ajuste de consulta ou bashing de teclado pode remediar. Um erro 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.

Ou talvez:

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

Esses Formula.Firewall erros são o resultado do Firewall de Privacidade de Dados do Power Query (também conhecido como Firewall), que às vezes pode parecer que existe apenas para frustrar analistas de dados em todo o mundo. Acredite ou não, no entanto, o Firewall serve a um propósito importante. Neste artigo, vamos nos aprofundar para entender melhor como funciona. Armado com maior compreensão, você esperançosamente será capaz de diagnosticar e corrigir melhor erros de Firewall no futuro.

O que é?

O objetivo do Firewall de Privacidade de Dados é simples: ele existe para impedir que o Power Query vaze dados involuntariamente entre fontes.

Porque é isto necessário? Quero dizer, você certamente poderia criar algum M que passaria um valor SQL para um feed OData. Mas isso seria vazamento intencional de dados. O autor do mashup saberia (ou pelo menos deveria) que estava fazendo isso. Porquê, então, a necessidade de proteção contra fugas não intencionais de dados?

A resposta? Dobrável.

Dobrável?

Dobragem é um termo que se refere à conversão de expressões em M (como filtros, renomeações, junções e assim por diante) em operações em uma fonte de dados bruta (como SQL, OData e assim por diante). Uma grande parte do poder do Power Query vem do fato de que o PQ pode converter as operações que um usuário executa por meio de sua interface de usuário em SQL complexo ou outras linguagens de fonte de dados de back-end, sem que o usuário precise conhecer essas linguagens. Os usuários obtêm o benefício de desempenho das operações de fonte de dados nativas, com a facilidade de uso de uma interface do usuário onde todas as fontes de dados podem ser transformadas usando um conjunto comum de comandos.

Como parte do dobramento, PQ às vezes pode determinar que a maneira mais eficiente de executar um determinado mashup é pegar dados de uma fonte e passá-los para outra. Por exemplo, se você estiver unindo um pequeno arquivo CSV a uma enorme tabela SQL, provavelmente não deseja que o PQ leia o arquivo CSV, leia toda a tabela SQL e, em seguida, junte-os em seu computador local. Você provavelmente deseja que o PQ incorpore os dados CSV em uma instrução SQL e peça ao banco de dados SQL para executar a junção.

É assim que o vazamento não intencional de dados pode acontecer.

Imagine se você estivesse juntando dados SQL que incluíssem números de segurança social de funcionários com os resultados de um feed OData externo e, de repente, descobrisse que os números de segurança social do SQL estavam sendo enviados para o serviço OData. Más notícias, certo?

Este é o tipo de cenário que o Firewall pretende evitar.

Como é que isto funciona?

O Firewall existe para impedir que dados de uma fonte sejam enviados involuntariamente para outra fonte. Simples o suficiente.

Então, como cumpre essa missão?

Ele faz isso dividindo suas consultas M em algo chamado partições e, em seguida, aplicando a seguinte regra:

  • Uma partição pode acessar fontes de dados compatíveis ou fazer referência a outras partições, mas não a ambas.

Simples... mas confuso. O que é uma partição? O que torna duas fontes de dados "compatíveis"? E por que o Firewall deve se preocupar se uma partição deseja acessar uma fonte de dados e fazer referência a uma partição?

Vamos dividir isso e olhar para a regra acima, uma peça de cada vez.

O que é uma partição?

Em seu nível mais básico, uma partição é apenas uma coleção de uma ou mais etapas de consulta. A partição mais granular possível (pelo menos na implementação atual) é uma única etapa. As partições maiores às vezes podem abranger várias consultas. (Mais sobre isso mais adiante.)

Se não estiver familiarizado com os passos, pode visualizá-los à direita da janela do Editor do Power Query depois de selecionar uma consulta, no painel Passos Aplicados . As etapas acompanham tudo o que você fez para transformar seus dados em sua forma final.

Partições que fazem referência a outras partições

Quando uma consulta é avaliada com o Firewall ativado, o Firewall divide a consulta e todas as suas dependências em partições (ou seja, grupos de etapas). Sempre que uma partição faz referência a algo em outra partição, o Firewall substitui a referência por uma chamada para uma função especial chamada Value.Firewall. Em outras palavras, o Firewall não permite que as partições acessem umas às outras diretamente. Todas as referências são modificadas para passar pelo Firewall. Pense no Firewall como um gatekeeper. Uma partição que faz referência a outra partição deve obter a permissão do Firewall para fazê-lo, e o Firewall controla se os dados referenciados serão ou não permitidos na partição.

Tudo isso pode parecer bastante abstrato, então vamos ver um exemplo.

Suponha que você tenha uma consulta chamada Funcionários, que extrai alguns dados de um banco de dados SQL. Suponha que você também tenha outra consulta (EmployeesReference), que simplesmente faz referência a Employees.

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

shared EmployeesReference = let
    Source = Employees
in
    Source;

Essas consultas terminarão divididas em duas partições: uma para a consulta Funcionários e outra para a consulta EmployeesReference (que fará referência à partição Funcionários). Quando avaliadas com o Firewall ativado, essas consultas serão reescritas da seguinte forma:

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

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

Observe que a referência simples à consulta Funcionários foi substituída por uma chamada para Value.Firewall, que é fornecido o nome completo da consulta Funcionários.

Quando EmployeesReference é avaliado, a chamada para Value.Firewall("Section1/Employees") é intercetada pelo Firewall, que agora tem a chance de controlar se (e como) os dados solicitados fluem para a partição EmployeesReference. Ele pode fazer qualquer número de coisas: negar a solicitação, armazenar em buffer os dados solicitados (o que impede que qualquer dobramento adicional para sua fonte de dados original ocorra) e assim por diante.

É assim que o Firewall mantém o controle sobre os dados que fluem entre partições.

Partições que acessam diretamente fontes de dados

Digamos que você defina uma consulta Query1 com uma etapa (observe que essa consulta de etapa única corresponde a uma partição do Firewall) e que essa única etapa acessa duas fontes de dados: uma tabela de banco de dados SQL e um arquivo CSV. Como o Firewall lida com isso, já que não há referência de partição e, portanto, nenhuma chamada para Value.Firewall intercetá-la? Vamos analisar a regra declarada anteriormente:

  • Uma partição pode acessar fontes de dados compatíveis ou fazer referência a outras partições, mas não a ambas.

Para que sua consulta de partição única, mas duas fontes de dados possa ser executada, suas duas fontes de dados devem ser "compatíveis". Em outras palavras, não há problema em que os dados sejam compartilhados bidirecionalmente entre eles. Isso significa que os níveis de privacidade de ambas as fontes precisam ser Públicos, ou ambos Organizacionais, uma vez que estas são as duas únicas combinações que permitem o compartilhamento em ambas as direções. Se ambas as fontes estiverem marcadas como Privadas, ou se uma estiver marcada como Público e outra estiver marcada como Organizacional, ou se forem marcadas usando alguma outra combinação de níveis de privacidade, o compartilhamento bidirecional não será permitido e, portanto, não é seguro que ambas sejam avaliadas na mesma partição. Fazer isso significaria que poderia ocorrer vazamento de dados inseguro (devido à dobragem), e o Firewall não teria como impedi-lo.

O que acontece se você tentar acessar fontes de dados incompatíveis na mesma partição?

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

Espero que agora você entenda melhor uma das mensagens de erro listadas no início deste artigo.

Observe que esse requisito de compatibilidade só se aplica dentro de uma determinada partição. Se uma partição estiver fazendo referência a outras partições, as fontes de dados das partições referenciadas não precisam ser compatíveis entre si. Isso ocorre porque o Firewall pode armazenar os dados em buffer, o que impedirá qualquer dobragem adicional em relação à fonte de dados original. Os dados serão carregados na memória e tratados como se viessem do nada.

Por que não fazer as duas coisas?

Digamos que você defina uma consulta com uma etapa (que corresponderá novamente a uma partição) que acessa duas outras consultas (ou seja, duas outras partições). E se você quisesse, na mesma etapa, também acessar diretamente um banco de dados SQL? Por que uma partição não pode fazer referência a outras partições e acessar diretamente fontes de dados compatíveis?

Como você viu anteriormente, quando uma partição faz referência a outra partição, o Firewall atua como o gatekeeper para todos os dados que fluem para a partição. Para tal, deve ser capaz de controlar quais os dados permitidos. Se houver fontes de dados sendo acessadas dentro da partição, e dados fluindo de outras partições, ela perde sua capacidade de ser o gatekeeper, uma vez que os dados que fluem podem ser vazados para uma das fontes de dados acessadas internamente sem que ele saiba disso. Assim, o Firewall impede que uma partição que acessa outras partições tenha permissão para acessar diretamente quaisquer fontes de dados.

Então, o que acontece se uma partição tentar fazer referência a outras partições e também acessar diretamente fontes de dados?

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.

Agora você espera entender melhor a outra mensagem de erro listada no início deste artigo.

Partições em profundidade

Como você provavelmente pode adivinhar a partir das informações acima, como as consultas são particionadas acaba sendo incrivelmente importante. Se você tiver algumas etapas que estão fazendo referência a outras consultas e outras etapas que acessam fontes de dados, agora você espera reconhecer que desenhar os limites da partição em determinados lugares causará erros de Firewall, enquanto desenhá-los em outros lugares permitirá que sua consulta seja executada corretamente.

Então, como exatamente as consultas são particionadas?

Esta seção é provavelmente a mais importante para entender por que você está vendo erros de Firewall e entender como resolvê-los (sempre que possível).

Aqui está um resumo de alto nível da lógica de particionamento.

  • Particionamento inicial
    • Cria uma partição para cada etapa em cada consulta
  • Fase estática
    • Esta fase não depende dos resultados da avaliação. Em vez disso, depende de como as consultas são estruturadas.
      • Corte de parâmetros
        • Corta partições com estilo de parâmetro, ou seja, qualquer uma que:
          • Não faz referência a nenhuma outra partição
          • Não contém invocações de função
          • Não é cíclico (ou seja, não se refere a si mesmo)
        • Note que "remover" uma partição efetivamente a inclui em quaisquer outras partições que a referenciam.
        • O corte de partições de parâmetros permite que as referências de parâmetros usadas em chamadas de função de fonte de dados (por exemplo, Web.Contents(myUrl)) funcionem, em vez de lançar erros de "partição não pode referenciar fontes de dados e outras etapas".
      • Agrupamento (Estático)
        • As partições são mescladas em ordem de dependência de baixo para cima. Nas partições mescladas resultantes, o seguinte será separado:
          • Partições em consultas diferentes
          • Partições que não fazem referência a outras partições (e, portanto, têm permissão para acessar uma fonte de dados)
          • Partições que fazem referência a outras partições (e, portanto, são proibidas de acessar uma fonte de dados)
  • Fase dinâmica
    • Esta fase depende dos resultados da avaliação, incluindo informações sobre fontes de dados acedidas por várias partições.
    • Corte
      • Corta partições que atendem a todos os seguintes requisitos:
        • Não acessa nenhuma fonte de dados
        • Não faz referência a nenhuma partição que acesse fontes de dados
        • Não é cíclico
    • Agrupamento (Dinâmico)
      • Agora que partições desnecessárias foram cortadas, tente criar partições de origem que sejam tão grandes quanto possível. Isso é feito mesclando as partições usando as mesmas regras descritas na fase de agrupamento estático acima.

O que significa tudo isto?

Vamos percorrer um exemplo para ilustrar como funciona a lógica complexa apresentada acima.

Aqui está um cenário de exemplo. É uma mesclagem bastante simples de um arquivo de texto (Contatos) com um banco de dados SQL (Funcionários), onde o servidor SQL é um parâmetro (DbServer).

As três questões

Aqui está o código M para as três consultas usadas neste exemplo.

shared DbServer = "MySqlServer" 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";

Aqui está uma visão de nível superior, mostrando as dependências.

Caixa de diálogo Dependências de Consulta.

Vamos particionar

Vamos ampliar um pouco e incluir etapas na imagem, e começar a percorrer a lógica de particionamento. Aqui está um diagrama das três consultas, mostrando as partições iniciais do firewall em verde. Observe que cada etapa começa em sua própria partição.

Partições iniciais de firewall.

Em seguida, cortamos partições de parâmetros. Assim, DbServer fica implicitamente incluído na partição Source.

Partições de firewall aparadas.

Agora realizamos o agrupamento estático. Isso mantém a separação entre partições em consultas separadas (observe, por exemplo, que as duas últimas etapas de Funcionários não são agrupadas com as etapas de Contatos) e entre partições que fazem referência a outras partições (como as duas últimas etapas de Funcionários) e aquelas que não fazem referência (como as três primeiras etapas de Funcionários).

Postar partições de firewall de agrupamento estático.

Agora entramos na fase dinâmica. Nesta fase, as partições estáticas acima são avaliadas. As partições que não acessam nenhuma fonte de dados são cortadas. As partições são então agrupadas para criar partições de origem que são tão grandes quanto possível. No entanto, neste cenário de exemplo, todas as partições restantes acessam fontes de dados e não há nenhum agrupamento adicional que possa ser feito. As partições em nosso exemplo, portanto, não mudarão durante esta fase.

Vamos fingir

Para ilustrar, porém, vamos ver o que aconteceria se a consulta Contatos, em vez de vir de um arquivo de texto, fosse codificada em M (talvez por meio da caixa de diálogo Inserir dados ).

Nesse caso, a consulta Contatos não acessaria nenhuma fonte de dados. Assim, ele seria cortado durante a primeira parte da fase dinâmica.

Partição de firewall após corte de fase dinâmica.

Com a partição Contatos removida, as duas últimas etapas de Funcionários não fariam mais referência a nenhuma partição, exceto a que continha as três primeiras etapas de Funcionários. Assim, as duas partições seriam agrupadas.

A partição resultante ficaria assim.

Partições finais de firewall.

Exemplo: Passando dados de uma fonte de dados para outra

Ok, chega de explicação abstrata. Vejamos um cenário comum em que é provável que você encontre um erro de Firewall e as etapas para resolvê-lo.

Imagine que você deseja procurar um nome de empresa no serviço Northwind OData e, em seguida, usar o nome da empresa para executar uma pesquisa do Bing.

Primeiro, você cria uma consulta Empresa para recuperar o nome da 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

Em seguida, você cria uma consulta de Pesquisa que faz referência à Empresa e a passa para o Bing.

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

Neste ponto, você se depara com problemas. A avaliação da Pesquisa produz um erro 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.

Isso ocorre porque a etapa Origem da Pesquisa está referenciando uma fonte de dados (bing.com) e também fazendo referência a outra consulta/partição (Empresa). Está violando a regra mencionada acima ("uma partição pode acessar fontes de dados compatíveis ou fazer referência a outras partições, mas não ambas").

O que fazer? Uma opção é desativar completamente o Firewall (através da opção Privacidade rotulada Ignorar os Níveis de Privacidade e potencialmente melhorar o desempenho). Mas e se você quiser deixar o Firewall ativado?

Para resolver o erro sem desativar o Firewall, você pode combinar Empresa e Pesquisa em uma única consulta, como esta:

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

Tudo agora está acontecendo dentro de uma única partição. Supondo que os níveis de privacidade para as duas fontes de dados sejam compatíveis, o Firewall agora deve estar satisfeito e você não receberá mais um erro.

Isso é um embrulho

Embora haja muito mais que poderia ser dito sobre este tópico, este artigo introdutório já é longo o suficiente. Espero que ele lhe dê uma melhor compreensão do Firewall, e irá ajudá-lo a entender e corrigir erros de Firewall quando você encontrá-los no futuro.