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 »
business cargo cargo container city

Como fazer uma imagem Docker com multi stage build

Estando todos vivendo numa era onde computação em nuvem vem sendo cada vez mais adotada, saber trabalhar com containers acaba se torna praticamente uma obrigação.

Por isso, nesse post, vamos ver como criar uma imagem Docker otimizada com multi stage build para aplicações escritas em Golang.

Antes de começar escrever nosso Dockerfile, vamos criar um arquivo main.go e escrever uma pequena API para retornar o famoso “Olá Mundo”.

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(":8000", 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 »

Benchmark dos routers http: chi vs gorilla mux

Até pouco tempo atrás eu nunca tinha ouvido falar sobre o go-chi. Foi durante uma reunião de trabalho que o Marcos Filho comentou sobre. Na mesma hora eu já anotei aqui na lista de idéias de posts para o blog para fazer uma comparação entre ele e o gorilla/mux.

Meus testes foram basicamente escrever um simples server http com a rota /{name}. Nesse server utilizei a ferramenta wrk para um teste de carga. Também escrevi um benchmark da própria linguagem para verificar, além de quanta porrada ele aguenta, o quanto de recurso os routers consumem.

Para fica mais simples, vou separar as comparações em três partes, onde nas duas primeiras vou apresentar os resultados individuais e por fim uma conclusão.

Então, para começar, vamos ver os resultados do router mais famoso do mundo Go.

Leia mais »

Buildando aplicações com Bazel (parte 1)

Bazel é um ferramenta criada e mantida pela Google que ajuda no processo de build de várias linguagens, sendo uma delas nosso querido Golang.

Duas de suas grandes vantagens são:

  • Build de multiplas aplicações em monorepo sem precisar ficar entrando e saindo de pastas.
  • Cache remoto das etapas de build (para mais detalhes, leia o post “Como Bazel funciona internamente“).

Embora para maioria das linguagens toda criação e manutenção dos arquivos do Bazel tenha que ser feita manualmente, para o Go temos o gazelle, uma ferramenta que nos auxilia nesse processo.

Se você ainda não tem o Bazel instalado na sua máquina, siga o tutorial do próprio site oficial segundo o OS que você utiliza. Se você já tem, execute um bazel version para garantir que você está utilizando a última versão (4.2.2).

Como aplicação exemplo, vamos utilizar o mesmo código do post Implementando uma API com gorilla/mux, mas separando as duas funções de “handler” em um novo package chamado handlers.

Leia mais »

Como encontrar pontos de melhoria de performance

Nesse post vamos falar sobre a técnica de profiling, que consegue nos ajudar muito na hora de encontrar melhorias em relação a performance dos nossos programas, principalmente, consumo de memória e CPU.

Para realizar os testes, vamos recuperar o código que escrevemos no post “Qual a melhor forma de aumentar um array?

Apenas para relembrar, nesse post escrevemos 3 funções e 3 testes, um para cada função. Cada uma das funções mostrar uma forma diferente de expandir um array.

Tendo relembrado isso, vamos agora fazer o profiling de cada uma das funções e analisar o resultado.

Para coletar dados de cpu e memória, vamos adicionar as flags -memprofile e -cpuprofile no comando que executamos para fazer benchmark das funções.

Leia mais »

Benchmark: API com gorilla mux usando goroutines vs sem goroutines

Já faz um certo tempo que eu queria dedicar algumas horas para testar um cenário onde os dados que uma request deveria apresentar fossem obtidos com goroutines vs sem goroutines.

Finalmente esse dia chegou, mas antes de apresentar os resultados, vamos construir juntos uma simples API onde vamos executar os testes para medir a performance.

O objetivo da request será obter o nome e a quantidade total de pedidos que uma pessoa já realizou.

Para não ter que envolver banco de dados, vamos criar duas variáveis contendo os dados que podemos retornar.

var (
    people = [][]string{
        []string{"1", "Tiago Temporin"},
        []string{"2", "João Silva"},
        []string{"3", "Mateus Cardoso"},
        []string{"4", "Maria Lina"},
        []string{"5", "Camila Manga"},
        []string{"6", "Joice Santos"},
        []string{"7", "Lucas Leal"},
        []string{"8", "Vanessa da Terra"},
        []string{"9", "Mateus de Morais"},
        []string{"10", "Maria Luiza"},
    }

    orders = [][]string{
        []string{"1", "5"},
        []string{"2", "10"},
        []string{"3", "0"},
        []string{"4", "0"},
        []string{"5", "2"},
        []string{"6", "9"},
        []string{"7", "3"},
        []string{"8", "15"},
        []string{"9", "3"},
        []string{"10", "7"},
    }
)
Leia mais »

Otimizando funções com memoize

Em ciência da computação, memoize ou memoization é uma técnica de otimização que faz um cache do resultado de uma função com base nos parâmetros passados para ela.

Essa técnica faz com que a execução real da função só aconteça a primeira vez que o parâmetro ou conjunto de parâmetros é passado, pois como fará um cache do resultado, ao receber os mesmos parâmetros, retornará o valor que está armazenado no cache.

Antes de utilizar a técnica, vamos criar duas funções. A Primeira para calcular o fatorial de um número.

func fatorial(n int) int {
    total := 1
    for i := 2; i &lt;= n; i++ {
        total *= i
    }

    return total
}
Leia mais »

Qual a diferença de length e capacity

Quando trabalhamos com slices, existem duas funções essenciais que utilizamos para “medir” o tamanho (length) e a capacidade (capacity) de um slice, que são len e cap, respectivamente.

Mas afinal, qual a diferença de length e capacity?

A documentação do Go define length como sendo a quantidade de elementos em um slice, enquanto capacity é a capacidade do array para abrigar elementos.

No exemplo abaixo vamos criar 2 slices. No primeiro vamos definir somente o length, enquanto no segundo vamos definir o length e a capacity.

Leia mais »

Como fazer benchmark do seu código

Muitas vezes quando vamos escolher um novo framework ou alguma lib, buscamos benchmarks para que nosso programa tenha a melhor performance possível. Isso é ótimo! Mas você já parou para fazer um benchmark do seu programa para tentar entender onde ele pode ser otimizado? Não sabe como? Então vamos ver como fazer.

A funções de benchmark ficam dentro dos arquivos *_test.go e tem, por convenção, o nome BenchmarkSuaFunc.

Muito similar a quando escrevemos testes, vamos usar o pacote testing do go, mas especificamente vamos usar o testing.B como parâmetro da nossa função de benchmark.

Para começar vamos aproveitar uma das funções do post “Qual a melhor forma para aumentar um array?“.

Leia mais »