Compartilhar via


Orquestrar notebooks e modularizar código em notebooks

Saiba como orquestrar notebooks e modularizar código em notebooks. Veja exemplos e entenda quando usar métodos alternativos para orquestração de notebook.

Métodos de orquestração e modularização de código

A tabela a seguir compara os métodos disponíveis para orquestrar notebooks e modularizar código em notebooks.

Método Caso de uso Anotações
Trabalhos do Lakeflow Orquestração de bloco de anotações (recomendado) Método recomendado para orquestrar notebooks.
Dá suporte a fluxos de trabalho complexos com dependências de tarefa, agendamento e gatilhos. Fornece uma abordagem robusta e escalonável para cargas de trabalho de produção, mas requer instalação e configuração.
dbutils.notebook.run() Orquestração de laptop Use dbutils.notebook.run() se os Trabalhos não puderem dar suporte ao seu caso de uso, como repetição de notebooks sobre um conjunto dinâmico de parâmetros.
Inicia um novo trabalho efêmero para cada chamada, que pode aumentar a sobrecarga e não tem recursos avançados de agendamento.
Arquivos de workspace Modularização de código (recomendado) Método recomendado para modularizar código.
Modularizar código em arquivos de código reutilizáveis armazenados no workspace. Dá suporte ao controle de versão com repositórios e integração com IDEs para melhor depuração e teste de unidade. Requer configuração adicional para gerenciar caminhos de arquivo e dependências.
%run Modularidade do código Use %run se você não puder acessar arquivos de workspace.
Basta importar funções ou variáveis de outros notebooks executando-as diretamente. Útil para protótipos, mas pode levar a um código firmemente acoplado que é mais difícil de manter. Não dá suporte à passagem de parâmetro ou controle de versão.

%run Vs. dbutils.notebook.run()

O comando %run permite que você inclua outro notebook em um notebook. Você pode usar %run para modularizar seu código colocando funções de suporte em um bloco de anotações separado. Você também pode usá-lo para concatenar notebooks que implementam as etapas em uma análise. Quando você usa %run, o notebook chamado é executado imediatamente e as funções e variáveis definidas nele ficam disponíveis no notebook de chamada.

A dbutils.notebook API complementa %run porque permite passar parâmetros para e retornar valores de um notebook. Dessa maneira, você cria fluxos de trabalho e pipelines complexos com dependências. Por exemplo, você pode obter uma lista de arquivos em um diretório e passar os nomes para outro bloco de anotações, o que é impossível com %run. Você também pode criar fluxos de trabalho if-then-else com base em valores retornados.

Ao contrário do %run, o método dbutils.notebook.run() inicia um novo trabalho para executar o notebook.

Assim como todas as dbutils APIs, esses métodos estão disponíveis apenas em Python e Scala. No entanto, você pode usar dbutils.notebook.run() para invocar um notebook R.

Usar %run para importar um notebook

Nesse exemplo, o primeiro notebook define uma função, reverse, que fica disponível no segundo notebook depois que você usa o magic %run para executar shared-code-notebook.

Código compartilhado do notebook

Exemplo de importação do notebook

Como ambos os notebooks estão no mesmo diretório no workspace, use o prefixo ./ em ./shared-code-notebook para indicar que o caminho deve ser resolvido em relação ao notebook atualmente em execução. Você pode organizar os notebooks em diretórios, como %run ./dir/notebook, ou usar um caminho absoluto, como %run /Users/username@organization.com/directory/notebook.

Observação

  • %run deve estar em uma célula sozinha, pois ele executa o notebook inteiro embutido.
  • Você não pode usar %run para executar um arquivo Python e import as entidades definidas nesse arquivo em um notebook. Para importar de um arquivo Python, consulte Modularizar seu código usando arquivos. Ou empacote o arquivo em uma biblioteca Python, crie uma biblioteca do Azure Databricks por meio dessa biblioteca Python e instale a biblioteca no cluster que você usa para executar o notebook.
  • Quando você usa %run para executar um notebook que contém widgets, por padrão, o notebook especificado é executado com os valores padrão do widget. Você também pode passar valores para widgets; consulte Usar widgets do Databricks com %run.

Usar dbutils.notebook.run para iniciar um novo trabalho

Executar um notebook e retornar o valor de saída. O método inicia um trabalho efêmero que é executado imediatamente.

Os métodos disponíveis na API dbutils.notebook são run e exit. Os parâmetros e os valores de retorno devem ser cadeias de caracteres.

run(path: String, timeout_seconds: int, arguments: Map): String

O timeout_seconds parâmetro controla o tempo limite da execução (0 significa que não há tempo limite). A chamada para run lança uma exceção se não for concluída dentro do tempo especificado. Se o Azure Databricks ficar inoperante por mais de 10 minutos, a execução do notebook falhará, independentemente de timeout_seconds.

O parâmetro arguments define valores de widget do notebook de destino. Especificamente, se o notebook que estiver executando tiver um widget chamado A e você passar um par chave-valor ("A": "B") como parte do parâmetro de argumentos para a chamada run(), a recuperação do valor do widget A retornará "B". Veja instruções para criar e trabalhar com widgets no artigo Widgets do Databricks.

Observação

  • O parâmetro arguments aceita apenas caracteres latinos (conjunto de caracteres ASCII). O uso de caracteres não ASCII retorna um erro.
  • Os trabalhos criados usando a API dbutils.notebook devem ser concluídos em 30 dias ou menos.

runUso

Python

dbutils.notebook.run("notebook-name", 60, {"argument": "data", "argument2": "data2", ...})

Scala (linguagem de programação)

dbutils.notebook.run("notebook-name", 60, Map("argument" -> "data", "argument2" -> "data2", ...))

Passar dados estruturados entre notebooks

Esta seção ilustra como passar dados estruturados entre notebooks.

Python

# Example 1 - returning data through temporary views.
# You can only return one string using dbutils.notebook.exit(), but since called notebooks reside in the same JVM, you can
# return a name referencing data stored in a temporary view.

## In callee notebook
spark.range(5).toDF("value").createOrReplaceGlobalTempView("my_data")
dbutils.notebook.exit("my_data")

## In caller notebook
returned_table = dbutils.notebook.run("LOCATION_OF_CALLEE_NOTEBOOK", 60)
global_temp_db = spark.conf.get("spark.sql.globalTempDatabase")
display(table(global_temp_db + "." + returned_table))

# Example 2 - returning data through DBFS.
# For larger datasets, you can write the results to DBFS and then return the DBFS path of the stored data.

## In callee notebook
dbutils.fs.rm("/tmp/results/my_data", recurse=True)
spark.range(5).toDF("value").write.format("parquet").save("dbfs:/tmp/results/my_data")
dbutils.notebook.exit("dbfs:/tmp/results/my_data")

## In caller notebook
returned_table = dbutils.notebook.run("LOCATION_OF_CALLEE_NOTEBOOK", 60)
display(spark.read.format("parquet").load(returned_table))

# Example 3 - returning JSON data.
# To return multiple values, you can use standard JSON libraries to serialize and deserialize results.

## In callee notebook
import json
dbutils.notebook.exit(json.dumps({
  "status": "OK",
  "table": "my_data"
}))

## In caller notebook
import json

result = dbutils.notebook.run("LOCATION_OF_CALLEE_NOTEBOOK", 60)
print(json.loads(result))

Scala (linguagem de programação)

// Example 1 - returning data through temporary views.
// You can only return one string using dbutils.notebook.exit(), but since called notebooks reside in the same JVM, you can
// return a name referencing data stored in a temporary view.

/** In callee notebook */
sc.parallelize(1 to 5).toDF().createOrReplaceGlobalTempView("my_data")
dbutils.notebook.exit("my_data")

/** In caller notebook */
val returned_table = dbutils.notebook.run("LOCATION_OF_CALLEE_NOTEBOOK", 60)
val global_temp_db = spark.conf.get("spark.sql.globalTempDatabase")
display(table(global_temp_db + "." + returned_table))

// Example 2 - returning data through DBFS.
// For larger datasets, you can write the results to DBFS and then return the DBFS path of the stored data.

/** In callee notebook */
dbutils.fs.rm("/tmp/results/my_data", recurse=true)
sc.parallelize(1 to 5).toDF().write.format("parquet").save("dbfs:/tmp/results/my_data")
dbutils.notebook.exit("dbfs:/tmp/results/my_data")

/** In caller notebook */
val returned_table = dbutils.notebook.run("LOCATION_OF_CALLEE_NOTEBOOK", 60)
display(sqlContext.read.format("parquet").load(returned_table))

// Example 3 - returning JSON data.
// To return multiple values, use standard JSON libraries to serialize and deserialize results.

/** In callee notebook */

// Import jackson json libraries
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper
import com.fasterxml.jackson.databind.ObjectMapper

// Create a json serializer
val jsonMapper = new ObjectMapper with ScalaObjectMapper
jsonMapper.registerModule(DefaultScalaModule)

// Exit with json
dbutils.notebook.exit(jsonMapper.writeValueAsString(Map("status" -> "OK", "table" -> "my_data")))

/** In caller notebook */

// Import jackson json libraries
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper
import com.fasterxml.jackson.databind.ObjectMapper

// Create a json serializer
val jsonMapper = new ObjectMapper with ScalaObjectMapper
jsonMapper.registerModule(DefaultScalaModule)

val result = dbutils.notebook.run("LOCATION_OF_CALLEE_NOTEBOOK", 60)
println(jsonMapper.readValue[Map[String, String]](result))

Tratar erros

Esta seção ilustra como manipular erros.

Python

# Errors throw a WorkflowException.

def run_with_retry(notebook, timeout, args = {}, max_retries = 3):
  num_retries = 0
  while True:
    try:
      return dbutils.notebook.run(notebook, timeout, args)
    except Exception as e:
      if num_retries > max_retries:
        raise e
      else:
        print("Retrying error", e)
        num_retries += 1

run_with_retry("LOCATION_OF_CALLEE_NOTEBOOK", 60, max_retries = 5)

Scala (linguagem de programação)

// Errors throw a WorkflowException.

import com.databricks.WorkflowException

// Since dbutils.notebook.run() is just a function call, you can retry failures using standard Scala try-catch
// control flow. Here, we show an example of retrying a notebook a number of times.
def runRetry(notebook: String, timeout: Int, args: Map[String, String] = Map.empty, maxTries: Int = 3): String = {
  var numTries = 0
  while (true) {
    try {
      return dbutils.notebook.run(notebook, timeout, args)
    } catch {
      case e: WorkflowException if numTries < maxTries =>
        println("Error, retrying: " + e)
    }
    numTries += 1
  }
  "" // not reached
}

runRetry("LOCATION_OF_CALLEE_NOTEBOOK", timeout = 60, maxTries = 5)

Executar vários notebooks simultaneamente

Você pode executar vários notebooks ao mesmo tempo usando constructos padrão do Scala e do Python, como Threads (Scala, Python) e Futuros (Scala, Python). Os notebooks de exemplo demonstram como usar esses constructos.

  1. Baixe os quatro blocos de anotações a seguir. Os notebooks são escritos em Scala.
  2. Importe os notebooks para uma única pasta no workspace.
  3. Execute o notebook Executar simultaneamente .

Executar notebook simultaneamente

Obter notebook

Executar no notebook paralelo

Obter notebook

Testando notebook

Obter notebook

Notebook test-2

Obter notebook