O envio de um arquivo é feito pelo sistema operacional. Ele apenas atola o conteúdo no soquete e não requer sobrecarga do seu aplicativo. É literalmente fogo e esqueça.
Enviar um fluxo de dados do servidor de banco de dados é consideravelmente mais complicado. A consulta deve ser composta, codificada, enviada pelo soquete, interpretada pelo servidor, executada e o conjunto de resultados deve ser recodificado e enviado de volta pela rede. Talvez você esteja deduzindo esse tempo do seu cálculo, mas não está claro.
Em seguida, uma vez que o cliente tenha recebido o resultado, ele deve lê-lo no soquete, convertê-lo em objetos Ruby, possivelmente modelos que geram ainda mais sobrecarga e entregar esse objeto de resultado de volta ao método solicitante. Se você estiver re-serializando-o em um resultado, ele terá que passar pelo processo de copiar esses dados uma segunda vez para o fluxo de saída.
Então, em resumo, lendo de um arquivo pré-existente: Zero lê, zero cópias.
Leitura do banco de dados: uma leitura, duas cópias.