O DuckDB é conhecido como o “SQLite para Analytics”. Quando integrado ao ecossistema Laravel dentro de containers, ele permite realizar consultas pesadas e exportações massivas sem degradar a performance do seu banco de dados principal.

1. Arquitetura da Solução Link para o cabeçalho

A estratégia consiste em rodar o DuckDB via CLI dentro do container PHP, comunicando-se com o container MySQL através da rede interna do Docker.

2. Configuração do Ambiente (Docker & Dockerfile) Link para o cabeçalho

O maior desafio é garantir que as extensões do DuckDB estejam disponíveis offline. No Dockerfile, instalamos o binário e pré-configuramos o diretório de extensões.


### Dockerfile (Trecho DuckDB)

# Instalação do DuckDB CLI
ARG DUCKDB_VERSION="v1.4.3"
ENV DUCKDB_EXTENSION_DIRECTORY=/app/duckdb/extensions

RUN wget https://github.com/duckdb/duckdb/releases/download/${DUCKDB_VERSION}/duckdb_cli-linux-amd64.zip -O /tmp/duckdb.zip && \
    dnf install -y unzip gzip && \
    unzip /tmp/duckdb.zip -d /usr/local/bin/ && \
    chmod +x /usr/local/bin/duckdb && \
    rm /tmp/duckdb.zip

# Estrutura para Extensões e Banco de Dados
RUN mkdir -p /app/duckdb/database && chmod 777 /app/duckdb/database
RUN mkdir -p /app/duckdb/extensions
RUN chmod -R 777 /app/duckdb/extensions
RUN duckdb -c "SET extension_directory='/app/duckdb/extensions'; INSTALL mysql;"

No docker-compose.yml, garantimos a persistência:

services:
  php74:
    volumes:
      - .:/app
      - /app/duckdb/extensions # Volume anônimo para proteger extensões do build
      - ./.docker/duckdb/database:/app/duckdb/database

3. Implementação: Repository e Service Link para o cabeçalho

Para manter o código limpo (Clean Code), separamos a execução do shell da lógica de negócio.

DuckDbRepository.php Link para o cabeçalho

Este arquivo lida com a “sujeira” de executar comandos via proc_open e garante que a porta do MySQL seja tratada como inteiro para evitar o erro stoi.

public function execute(string $query): array {
    $port = (int) env('DB_PORT', 3306);
    $fullSql = "
        SET extension_directory='/app/duckdb/extensions';
        SET max_memory='1.5GB'; -- Configuração de limite de memória inserida aqui
        SET threads=2; -- Limita a 2 núcleos de processamento
        LOAD mysql_scanner;
        ATTACH 'host=".env('DB_HOST')." user=".env('DB_USERNAME')." password=".env('DB_PASSWORD')." port=$port database=".env('DB_DATABASE')."' AS my_db (TYPE MYSQL_SCANNER);
        $query
    ";
    // Execução via proc_open e retorno de json_decode...
}

DuckDbAnalyticsService.php Link para o cabeçalho

A camada de serviço expõe métodos de alto nível para a aplicação.

public function getTableInventory(): array {
    return $this->duckDb->execute("SELECT table_name FROM information_schema.tables WHERE table_schema = 'db_app';");
}

public function getUserStats(): array {
    return $this->duckDb->execute("SELECT count(*) as total FROM my_db.users;");
}

4. Testes de Evolução do Código Link para o cabeçalho

Abaixo, a jornada de como o código evoluiu de um comando simples para uma estrutura profissional.

Fase 1: TestDuckDB (O Teste de Sanidade) Link para o cabeçalho

O primeiro teste apenas valida se o binário está acessível e executando funções básicas de sistema.

  • Comando: duckdb -c "SELECT 1;"

  • Objetivo: Validar se o shell_exec do PHP consegue invocar o DuckDB.

Fase 2: TestDuckDBToMysql (A Ponte Direta) Link para o cabeçalho

Nesta fase, testamos a conexão direta no comando. Foi aqui que descobrimos a necessidade de usar o mysql_scanner e o ATTACH correto.

  • Desafio: Resolver o download da extensão e a conectividade de rede.

  • Resultado: Sucesso ao listar tabelas via information_schema.

Fase 3: TestDuckDBMysqlService (A Estrutura Final) Link para o cabeçalho

Aqui, o comando Laravel torna-se apenas um “cliente” do Service.

public function handle(DuckDbAnalyticsService $service) {
    $this->info('🔗 Iniciando via Service/Repository...');
    $tabelas = $service->getTableInventory();
    $this->table(['Tabela'], $tabelas);
}

5. Conclusão e Próximos Passos Link para o cabeçalho

Com essa estrutura, você superou os obstáculos de permissões Docker, instalação de extensões offline e erros de conversão de tipos (stoi). Agora, sua aplicação está pronta para:

  1. Gerar arquivos Parquet: Transformar tabelas MySQL em arquivos locais ultra-compactos.

  2. Dashboards Pesados: Consultar milhões de linhas em milissegundos.

  3. Data Warehouse Local: Manter um histórico de dados sem onerar o banco de produção.