Fala dataholics, mais uma dica de milhões para vocês, como atuo diretamente na administração e sustentação de Databricks e outras ferramentas, geralmente pegamos casos legais todos os dias, tentarei compartilhar mais casos com vocês, hoje vou mostrar um tema onde ajudei alguns engenheiros e foi um caso bem interessante.
O que veremos no post de hoje?
dbutils.notebook.run() e como funciona
%run e como funciona
Enviando parâmetros para notebooks
Usando Widgets
Limitações
Spark Context e Spark Session
Resumo da utilização de cada um
Você já precisou realizar a chamada de algum notebook dentro de outro notebook? Se sim, já se deparou com esses nomes que citei no título, caso não, temos 2 opções e vou explicar sobre elas.
A primeira opção e mais simples, pois podemos enviar parâmetros de forma facilitada é o dbutils.notebook.run(). Com essa função você executa um notebook imediatamente, porém, o que esta por de trás dos panos? Vamos ver agora.
Tenho o primeiro notebook chamado notebook1 onde definimos algumas variáveis, agora queremos chamar o notebook2 passando essas variáveis como parâmetros, observe no comando 2, estou passando o caminho do notebook e enviando um valor de 30 que é um timeout, ou seja, ele vai esperar 30 segundos até falhar por timeout e tenho uma lista de parâmetros informando as duas variáveis criadas no comando 1.
E no notebook2 estou apenas pegando os valores dos parâmetros recebidos e mostrando na tela.
Esse é um exemplo bem simplista, contudo, em casos mais reais existem vários processamentos e lógicas de negócio dentro desse notebook2, vai depender do seu desenvolvimento e necessidade.
Observe que estou usando tanto a função getArgument() quanto a widgets.get(), só para mostrar que é o mesmo efeito, eu falo um pouco disso neste post de parametrização.
Bom vamos à execução desse notebook1:
Primeiro ponto a notar, ele criou um Job Run? Mas o que é isso?
Se você clicar no link vai ver todos os detalhes da execução, tudo como se fosse um Job do Workflows, rodando no cluster All Purpose que você esta conectado, é exatamente um job do Workflows, mas sem estar criado no Workflows, só existe em tempo de execução.
O comando dbutils.notebook.run() chama a API de Job Runs, por isso você consegue enviar parâmetros de forma simplificada.
O resultado é o que esperávamos, ele printou as variáveis enviadas pelo notebook1 e sucesso.
Agora vamos aprofundar o cenário, para melhor gestão do ambiente realizamos o bloqueio de criação de Jobs nos clusters All Purpose, para ninguém mais criar Jobs sem passar pelo time de administração, o objetivo era somente bloquear a criação e agendamento de novos Jobs, contudo, isso impactou no comando dbutils.notebook.run(), o engenheiro recebia o seguinte erro:
INVALID_PARAMETER_VALUE: The cluster xxxxxxxx does not support jobs workload
Bloquear a criação de Jobs resolveu nosso problema de controle do ambiente, mas gerou esse outro. Foi aí que sugeri o engenheiro ao invés de usar o dbutils.notebook.run e passar a usar o %run.
Contudo, não foi tão simples assim, o %run se comporta diferente do dbutils.notebook.run, ele não cria uma Job Run, ele basicamente roda o notebook referenciado como se fosse um comando só, tudo dentro da mesma sessão e Contexto do Spark, o %run é bem útil no dia a dia, você pode criar notebooks de funções ou parametrizações default e depois só chamar ele nos demais notebooks, isso facilita na organização de alguns cenários.
Porém, o problema aqui era outro, como enviar parâmetros usando o %run?
Simples assim! SQN!
Note que na chamada acima estou passando um valor fixo, infelizmente não encontramos uma forma de enviar variáveis, aparentemente não tem suporte.
Ele simplesmente envia um valor literal fixo, note que ele não envia o valor da variável, ele envia os nomes.
Bom, pensei então, vamos usar Widgets.
Então defini os Widgets no meu notebook1, na chamada do notebook2 não envio mais parâmetros, pois, ele vai usar os Widgets definidos no notebook1, como ele roda dentro da mesma Sessão do Spark os Widgets ficam visíveis para todos os notebooks chamados via %run, diferente do notebooks.run().
Funcionou top! SQN!
Parece que funcionou, mas temos outro problema, quando trocamos os valores do Widget ele não atualiza.
Veja que trocamos as datas, mas o Widget contínua com valores desatualizados.
Bom tentei mais algumas coisas, inclusive um dbutils.widgets.removeAll() para limpar as referências, mas não deu bom, de fato temos uma limitação aqui:
Mas somos de TI né, nada pode ficar sem solução, então pensei numa solução relativamente simples, criar uma tabela de controle, onde ela grava os parâmetros e recupera dentro do notebook chamado, assim funcionou sem problemas.
O exemplo é bem simples, cria uma tabela de parâmetros, insere os valores que deseja enviar.
drop table if exists tb_parameters;
create table if not exists tb_parameters (dataini date, datafim date);
insert into tb_parameters values('2023-01-01','2023-03-31');
O notebook de referência ficou assim:
E a chamada funciona tranquilamente e você pode ajustar os valores a qualquer momento na tabela que eles serão refletidos.
Ficou curioso sobre as sessões do Spark também?
Utilizando dbutils.notebook.run():
Abaixo temos a sessão e contexto do Spark para o notebook1.
Sessão: 547e3122
Contexto: 75de685d
Quando chamo o notebook 2:
Sessão: 4f272725
Contexto: 75de685d
Então ele cria uma nova sessão do Spark quando roda via Job Run, mas usando o mesmo contexto.
E com o %Run:
Sessão: 547e3122
Contexto: 75de685d
Ou seja, com %Run ele usa a mesma sessão e contexto do Spark, como se tivesse tudo dentro do mesmo notebook.
Resumindo o que vimos até aqui:
dbutils.notebook.run():
Executa uma chamada na API Job Runs e inicia uma nova sessão Spark para rodar o notebook, aceita parâmetros como se fosse um job normal, não podemos retornar as funções e variáveis para o notebook pai que o executou.
%Run:
Roda dentro da mesma sessão do Spark, traz todo o conteúdo do notebook chamado para dentro do notebook pai, exemplo, funções e variáveis, como se unificasse os notebooks.
Não consegue enviar parâmetros de forma dinâmica e tem problemas com uso de Widgets.
É isso, espero que tenha ficado claro, qualquer coisa deixa um comentário ou manda mensagem.
Link do Github:
Fique bem e até a próxima.
Referencias:
https://medium.com/@achilleus/spark-session-10d0d66d1d24 (Spark Session e Context)
https://docs.databricks.com/notebooks/notebook-workflows.html#run (%run e notebook.run)
https://docs.databricks.com/notebooks/widgets.html#widgets-and-percent-run (Widgets limitation)