Benchmark: ORM vs SQL puro

Finalmente tive tempo para sanar, com dados, uma das minhas e, imagino que de várias outras pessoas, maiores dúvidas quando se trata de Go e banco de dados. Qual a diferença, ao nível de consumo de recurso e performance, entre utilizar GORM vs escrever SQL na unha.

Para ficar mais fácil a leitura, separei o post em tópicos. Iniciarei explicando como fiz o setup, as funções comuns e realizei a execução dos benchmarks. Depois, separo o código do benchmark, assim como o resultado, em ações de CRUD.

Setup

Primeiramente, criei os packages entities, orm e std. Dentro do package entities, criei uma struct para ser utilizada em todos os benchmarks.

package entities

type Category struct {
    ID          int64  `gorm:"column:id;primaryKey"`
    Name        string `gorm:"column:name"`
    Description string `gorm:"column:description"`
}
Leia mais »

Benchmark: conexão sempre aberta vs uma conexão por chamada

Como eu nunca havia visto um benchmark para mostrar as diferenças entre, abrir uma conexão no início do programa e utilizá-la como dependência e, abrir uma nova conexão a cada chamada, resolvi fazê-la.

Embora a maioria das pessoas que converso saberem a resposta correta, não sei se, assim como eu, elas têm a ideia de quão grande é a diferença entre essas abordagens.

Mas antes de ver o resultado, mostrarei como construí o benchmark.

Conexão & Dados

Bem simples, para abrir a conexão sqlite3, criei uma função chamada Open.

package connections  

import (  
    "database/sql"  

    _ "github.com/mattn/go-sqlite3"  
)  

// Open a connection with a sqlite3 database
func Open() (*sql.DB, error) {  
    return sql.Open("sqlite3", "test.db")  
}
Leia mais »
green tree

Qual a diferença entre reflect.TypeOf e reflect.ValueOf

Se você é novo no mundo Go, muito provavelmente ainda não tenha trabalhado diretamente com o package reflect. Na verdade, é completamente normal pessoas que já trabalhem com a linguagem a algum tempo e ainda não tenham utilizado tal package.

Isso por que esse package tem uma utilidade muito específica. Não que todos os packages não tenham, ou pelo menos deveriam ter. Mas no caso do reflect, sua especificidade é tão grande, que mesmo em sistemas que o utilizam, ele provavelmente fica em alguma parte obscura, complexa e pouco mantida.

Tendo dito tudo isso e, ao mesmo tempo falado muito pouco, nesse post explicarei tudo o que você precisa saber sobre o package reflect e suas funções TypeOf e ValueOf.

Reflect

Antes de entender o que as funções TypeOf e ValueOf tem a oferecer, falemos um pouco sobre a finalidade do package reflect.

Leia mais »
photo of gray faucet

Como resolver memory leaks em maps

Uma das formas mais comuns de se fazer cache em aplicações Go é utilizando um map. Se você já fez isso, deve ter notado um aumento gradual no consumo de memória, e que normalmente após um restart da máquina ou pod volta ao “normal”.

Isso acontece devido a forma como o map funciona. Por isso, antes de ver o que podemos fazer para resolver esse tipo de problema, vamos entender melhor o map.

Para exemplificar o problema, vamos considerar uma variável do tipo map[int][128]byte, que será “carregada” com 1 milhão de elementos e que na sequência serão removidas.

package main

import (
	"fmt"
	"runtime"
)

func main() {
	n := 1_000_000
	m := make(map[int][128]byte)
	printAlloc()

	for i := 0; i < n; i++ {
		m[i] = [128]byte{}
	}
	printAlloc()

	for i := 0; i < n; i++ {
		delete(m, i)
	}

	runtime.GC()
	printAlloc()
	runtime.KeepAlive(m)
}

func printAlloc() {
	var m runtime.MemStats
	runtime.ReadMemStats(&m)
	fmt.Printf("%d KB\\n", m.Alloc/1024)
}
Leia mais »
depth of field photography of file arrangement

Como listar arquivos de um diretório

Nesse post vou mostrar três formas diferentes para ler um diretório com Golang.

[SPOILER ALERT] No final desse post vou mostrar como eu apliquei um dos exemplos para poder ler um diretório que continha mais de 3.6 milhões de arquivos.

Vamos iniciar os exemplos utilizando a função ReadDir do package ioutil.

package main

import (
    "fmt"
    "io/ioutil"
    "log"
)

func main() {
    files, err := ioutil.ReadDir("/tmp/")
    if err != nil {
        log.Fatal(err)
    }

    for _, file := range files {
        fmt.Println(file.Name(), file.IsDir())
    }
}
Leia mais »
gray concrete road near forest

Mutex ou channels? Como resolver race condition

Existem basicamente duas formas para resolver problemas de race condition. Mutex e Channels.

Se você não está familiarizado com o termo race condition, não se preocupe. Esse termo é utilizado para descrever um pedaço do código que será executado por múltiplas goroutines e que, a cada execução, seu resultado pode variar devido a forma como o Go alterna a execução entre goroutines.

Para ficar um pouco mais claro, vamos dar uma olhada no código abaixo.

package main

import (
	"fmt"
	"sync"
)

var total = 0

func count(wg *sync.WaitGroup) {
	total++
	wg.Done()
}

func main() {
	var wg sync.WaitGroup

	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go count(&wg)
	}
	wg.Wait()
	fmt.Println("total: ", total)
}
Leia mais »
snow top mountain under clear sky

Como diminuir o tamanho da sua aplicação com ldflags

Como você deve ter visto no vídeo que postamos no canal, uma das formas de reduzir o tamanho de uma imagem docker para aplicações Go é utilizando a imagem scratch como base.

Mas e o binário? Como podemos reduzi-lo sem remover código?

É isso que vamos ver nesse post.

Para que tenhamos uma base de programa para testar os comandos que vamos ver nesse post, vamos escrever uma API bem simples.

package main

import (
	"fmt"
	"log"
	"net/http"
)

func main() {
	http.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(rw, "Olá Mundo\\n")
	})

	log.Fatal(http.ListenAndServe(":8080", nil))
}
Leia mais »
view of elephant in water

Como funciona a gestão de conexões com banco de dados do package database/sql

Uma coisa que sempre tive dúvidas era sobre como o package database/sql geria as conexões com banco de dados. A forma como eu costumava trabalhar era abrir uma nova conexão a cada request recebida, fechando-a ao final.

Mas será que essa é a melhor forma?

A resposta curta é NÃO. Mas como eu sei que você, assim como eu não se satisfaz com uma resposta simples dessa, vamos entender melhor como o package padrão do Go gerencia as conexões.

A primeira coisa que precisamos ter em mente, é que sempre que abrimos uma nova conexão utilizando o sql.Open, ele nos retornará uma struct do tipo *sql.DB.

Essa struct, além de ser segura para ser utilizada em múltiplas goroutines, ou seja, “thread-safe”, ela não lida somente com uma conexão, mas sim com um pool de conexões.

Leia mais »

Quando utilizar Goroutines?

Uma dúvida que assombra a maioria dos desenvolvedores Go, e não exclusivamente iniciantes, é sobre quando utilizar goroutines.

Nesse post vou dar algumas dicas para ajudar na análise e tomada de decisão na hora de adotar ou não a utilização de goroutines em seu projeto.

Antes de mais nada, assim como qualquer coisa relacionada a tecnologia, nem todo projeto faz sentido utilizar goroutines. Você pode até pensar, “meu sistema está lento. Já sei, vou usar goroutines para resolver.”, gastar muito tempo, já que SÓ colocar um go antes da chamada da função pode não ser o suficiente, e no final não ver melhora nenhuma ou até mesmo notar uma piora no desempenho.

Leia mais »