Ir para o conteúdo principal

Plugin do Lakebase

Plugin do Lakebase

Fornece um pool de conexões PostgreSQL para o Databricks Lakebase Autoscaling com refresh automático de token OAuth.

Principais recursos:

  • pg.Pool padrão, compatível com qualquer biblioteca ou ORM de PostgreSQL
  • Refresh automático de token OAuth (tokens de 1 hora, buffer de refresh de 2 minutos)
  • Cache de tokens para minimizar chamadas de API
  • Instrumentação OpenTelemetry integrada (duração das queries, conexões do pool, refresh de token)
  • Logger do AppKit configurado por padrão para eventos de query e de conexão

Primeiros passos com o Lakebase

A maneira mais fácil de começar a usar o plugin do Lakebase é usar a Databricks CLI para criar um novo app do Databricks já com o AppKit e o plugin do Lakebase instalados.

Pré-requisitos

Passos

  1. Primeiro, crie um novo projeto Lakebase Postgres Autoscaling conforme a documentação de introdução.
  2. Para adicionar o plugin do Lakebase ao seu projeto, execute o comando databricks apps init e selecione o plugin Lakebase no modo interativo. A CLI vai guiá-lo na escolha do projeto, da branch e do banco de dados do Lakebase.
    • Quando solicitado, selecione Yes para fazer o deploy do app no Databricks Apps logo após sua criação.

Uso básico

import { createApp, lakebase, server } from "@databricks/appkit";

await createApp({
  plugins: [server(), lakebase()],
});

Acessando o pool

Após a inicialização, acesse o Lakebase por meio do objeto AppKit.lakebase:

const AppKit = await createApp({
  plugins: [server(), lakebase()],
});

await AppKit.lakebase.query(`CREATE SCHEMA IF NOT EXISTS app`);

await AppKit.lakebase.query(`CREATE TABLE IF NOT EXISTS app.orders (
  id SERIAL PRIMARY KEY,
  user_id VARCHAR(255) NOT NULL,
  amount DECIMAL(10, 2) NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)`);

const result = await AppKit.lakebase.query(
  "SELECT * FROM app.orders WHERE user_id = $1",
  [userId],
);

// pg.Pool puro (para ORMs ou uso avançado)
const pool = AppKit.lakebase.pool;

// Objetos de configuração prontos para uso com ORMs
const ormConfig = AppKit.lakebase.getOrmConfig();  // { host, port, database, ... }
const pgConfig = AppKit.lakebase.getPgConfig();    // pg.PoolConfig

Configuração

Variáveis de ambiente

As variáveis de ambiente obrigatórias são:

VariávelDescrição
LAKEBASE_ENDPOINTCaminho do recurso do endpoint (por exemplo, projects/.../branches/.../endpoints/...)
PGHOSTHost do Lakebase (injetado automaticamente em produção pelo recurso postgres do Databricks Apps)
PGDATABASENome do banco de dados (injetado automaticamente em produção pelo recurso postgres do Databricks Apps)
PGSSLMODEModo TLS — defina como require (injetado automaticamente em produção pelo recurso postgres do Databricks Apps)

Ao fazer o deploy no Databricks Apps com um recurso de banco de dados postgres configurado, PGHOST, PGDATABASE, PGSSLMODE, PGUSER, PGPORT e PGAPPNAME são injetados automaticamente pela plataforma. Apenas LAKEBASE_ENDPOINT precisa ser definido explicitamente:

env:
  - name: LAKEBASE_ENDPOINT
    valueFrom: postgres

Para desenvolvimento local, o arquivo .env é gerado automaticamente pelo databricks apps init com os valores corretos do seu projeto Lakebase.

Para a referência completa de configuração (SSL, tamanho do pool, tempos limite, logging, exemplos de ORM), consulte o README do @databricks/lakebase.

Configuração do pool

Passe um objeto pool para sobrescrever qualquer valor padrão:

await createApp({
  plugins: [
    lakebase({
      pool: {
        max: 10,                      // Máximo de conexões do pool (padrão: 10)
        connectionTimeoutMillis: 5000, // Tempo limite de conexão em ms (padrão: 10000)
        idleTimeoutMillis: 30000,      // Tempo limite de conexão ociosa em ms (padrão: 30000)
      },
    }),
  ],
});

On-Behalf-Of (OBO) — conexões por usuário

Quando seu app precisa de Row-Level Security (RLS) ou isolamento de dados por usuário, use asUser(req) para executar queries em um pool de conexões Lakebase específico de cada usuário. O pool de cada usuário é autenticado com a respectiva identidade Databricks, de modo que o current_user do PostgreSQL reflete o usuário real.

Pré-requisitos

  1. Habilite a autorização de usuário no seu Databricks App com o escopo postgres. Consulte Autorização de usuário para obter as instruções de configuração. No seu databricks.yml:

    resources:
      apps:
        app:
          user_api_scopes:
            - postgres

    Apps criados com databricks apps init e o plugin do Lakebase já incluem isso automaticamente.

  2. Cada usuário do app precisa de um papel do Postgres no Lakebase. Crie um com o Databricks CLI:

    databricks postgres create-role "projects/{project_id}/branches/{branch_id}" \
      --json '{"spec": {"identity_type": "USER", "postgres_role": "user@example.com"}}'

    Outra opção é criar os papéis na UI do Lakebase, em Branch OverviewAdd role.

    note

    Não conceda databricks_superuser a usuários OBO — superusuários ignoram o RLS. Use fine-grained grants no lugar.

Uso

Nenhuma configuração é necessária — basta chamar asUser(req):

const AppKit = await createApp({
  plugins: [server(), lakebase()],
});

// Query do service principal (padrão — ignora o RLS como proprietário da tabela)
const all = await AppKit.lakebase.query("SELECT * FROM app.orders");

// Query no escopo do usuário (pool por usuário, com RLS aplicado)
app.get("/api/my-orders", async (req, res) => {
  const result = await AppKit.lakebase
    .asUser(req)
    .query("SELECT * FROM app.orders ORDER BY created_at DESC");
  res.json(result.rows);
});

Quando asUser(req) é chamado:

  1. O token e a identidade do usuário são extraídos dos cabeçalhos x-forwarded-access-token e x-forwarded-email (definidos automaticamente pelo Databricks Apps).
  2. É criado (ou reutilizado) um pg.Pool específico para cada usuário, com as credenciais OAuth do próprio usuário.
  3. query() e pool passam a usar o pool do usuário — o current_user no PostgreSQL reflete a identidade dele.

Exemplo de Row-Level Security

-- Como o service principal (durante o setup do app):
ALTER TABLE app.orders ENABLE ROW LEVEL SECURITY;

CREATE POLICY user_orders ON app.orders
  FOR ALL TO PUBLIC
  USING (owner = current_user);

-- Concede acesso para que usuários OBO possam executar queries
GRANT USAGE ON SCHEMA app TO PUBLIC;
GRANT SELECT, INSERT ON ALL TABLES IN SCHEMA app TO PUBLIC;

Como funciona

  • O pool do service principal (AppKit.lakebase.pool) é sempre criado e usado para operações DDL, seeding e queries administrativas.
  • Os pools por usuário são criados na primeira chamada a asUser(req) e ficam em cache por identidade de usuário. Cada pool tem seu próprio ciclo de OAuth token refresh.
  • As conexões ociosas dentro dos pools por usuário são fechadas automaticamente (timeout de inatividade de 30s). Objetos de pool vazios são limpos periodicamente.
  • No encerramento, todos os pools (SP + usuário) são fechados de forma controlada.
  • No modo de desenvolvimento (NODE_ENV=development), se nenhum token de usuário estiver disponível, asUser(req) recorre ao pool do SP e emite um aviso.
RLS e superusuários

Superusuários do PostgreSQL ignoram completamente a Row-Level Security. Usuários com a role databricks_superuser verão todas as linhas, independentemente das políticas de RLS. Para garantir a aplicação do RLS, use fine-grained grants em vez da role de superusuário.

Permissões do banco de dados

Ao criar o app com o recurso Lakebase seguindo o guia Primeiros passos, o service principal recebe automaticamente a permissão CONNECT_AND_CREATE no recurso postgres. Com isso, o service principal pode se conectar ao banco de dados e criar novos objetos, mas não acessar nenhum schema ou tabela já existente.

Desenvolvimento local

Para desenvolver localmente usando um banco de dados Lakebase já implantado:

  1. Faça o deploy do app primeiro. O service principal cria o schema e as tabelas do banco de dados no primeiro deploy. Apps gerados por databricks apps init cuidam disso automaticamente — verificam se as tabelas existem na inicialização e pulam a criação caso já existam.

  2. Conceda databricks_superuser (pule esta etapa se você for o proprietário do projeto Lakebase — nesse caso já tem acesso total):

    # Cria um novo role com databricks_superuser
    databricks postgres create-role "projects/{project_id}/branches/{branch_id}" \
      --json '{"spec": {"identity_type": "USER", "postgres_role": "user@example.com", "membership_roles": ["DATABRICKS_SUPERUSER"]}}'

    Para conceder superuser a um role existente, use update-role:

    databricks postgres update-role \
      "projects/{project_id}/branches/{branch_id}/roles/{role_id}" \
      "spec.membership_roles" \
      --json '{"spec": {"membership_roles": ["DATABRICKS_SUPERUSER"]}}'

    Como alternativa, você pode gerenciar os roles na UI do Lakebase Autoscaling, na página Branch Overview do seu projeto → Add role / Edit role.

  3. Execute localmente — sua identidade de usuário do Databricks (e-mail) é usada na autenticação OAuth. O role databricks_superuser concede acesso DML completo (leitura/escrita de dados), mas não DDL (criação de schemas ou tabelas) — é por isso que fazer o deploy primeiro faz diferença (veja a nota abaixo).

Para os demais usuários, repita o passo 2 e crie um role OAuth com databricks_superuser para cada um.

tip

A autenticação por senha do Postgres é uma alternativa mais simples, que evita a complexidade das permissões de roles OAuth. Em compensação, exige que você defina uma senha para o usuário na página Branch Overview, na UI do Lakebase Autoscaling.

Por que fazer o deploy primeiro?

Quando o app é implantado, o service principal cria os schemas e as tabelas e se torna proprietário deles. O databricks_superuser concede acesso DML completo (leitura/escrita), mas não DDL, então o desenvolvimento local só funciona depois que o schema existe.

Se você executar npm run dev antes, suas credenciais passam a ser as proprietárias do schema e o app implantado recebe permission denied. Para contornar isso, exporte os dados primeiro (pg_dump ou uma cópia temporária do schema), depois exclua o schema e faça o deploy novamente. Após o novo deploy, o service principal recria o schema na inicialização. (No PostgreSQL, a propriedade de um schema fica vinculada ao role que o criou e não pode ser reatribuída por usuários comuns.)

Permissões granulares

Na maioria dos casos de uso, databricks_superuser é suficiente. Se você precisar de grants em nível de schema, consulte a documentação oficial:

Script SQL para fine-grained grants

Faça o deploy e execute o app pelo menos uma vez antes de aplicar estes grants, para que o Service Principal inicialize o schema do banco de dados primeiro.

Substitua subject pelo e-mail do usuário e schema pelo nome do seu schema:

CREATE EXTENSION IF NOT EXISTS databricks_auth;

DO $$
DECLARE
  subject TEXT := 'your-subject';  -- E-mail do usuário, como name@databricks.com
  schema TEXT := 'your_schema'; -- Substitua 'your_schema' pelo nome do seu schema
BEGIN
  -- Cria a role OAuth para a identidade do Databricks
  PERFORM databricks_create_role(subject, 'USER');

  -- Acesso à conexão e ao schema
  EXECUTE format('GRANT CONNECT ON DATABASE "databricks_postgres" TO %I', subject);
  EXECUTE format('GRANT ALL ON SCHEMA %s TO %I', schema, subject);

  -- Privilégios em objetos existentes
  EXECUTE format('GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA %s TO %I', schema, subject);
  EXECUTE format('GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA %s TO %I', schema, subject);
  EXECUTE format('GRANT ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA %s TO %I', schema, subject);
  EXECUTE format('GRANT ALL PRIVILEGES ON ALL PROCEDURES IN SCHEMA %s TO %I', schema, subject);

  -- Privilégios padrão para objetos futuros
  EXECUTE format('ALTER DEFAULT PRIVILEGES IN SCHEMA %s GRANT ALL ON TABLES TO %I', schema, subject);
  EXECUTE format('ALTER DEFAULT PRIVILEGES IN SCHEMA %s GRANT ALL ON SEQUENCES TO %I', schema, subject);
  EXECUTE format('ALTER DEFAULT PRIVILEGES IN SCHEMA %s GRANT ALL ON FUNCTIONS TO %I', schema, subject);
  EXECUTE format('ALTER DEFAULT PRIVILEGES IN SCHEMA %s GRANT ALL ON ROUTINES TO %I', schema, subject);
END $$;

Databricks Developer Hub

Pronto para lançar seu próximo aplicativo baseado em agentes em minutos?

Ler a documentação