Jump to content

Featured Replies

Posted

As configurações do ambiente execução do Go podem impactar o comportamento de um serviço em determinado ambiente.

 

Entender quais configurações são necessárias é imprescindível quando se trata de garantir o máximo de eficiência e desempenho de um serviço escrito em Go.

 

A configuração [iCODE]GOMAXPROCS[/iCODE] dita o comportamento do serviço em relação ao consumo de [iCODE]CPU[/iCODE].

 

  • [iCODE]GOMAXPROCS[/iCODE]: Número de "[iCODE]threads[/iCODE]" disponíveis para o processo. O valor padrão é o número de núcleos da [iCODE]CPU[/iCODE].

 

Neste artigo, vou descrever minha experiência em busca da configuração ideal para um serviço hospedado no Kubernetes.

 

 

 

[HEADING=1]GOMAXPROCS e goroutines[/HEADING]

 

A documentação oficial do Go refere-se às goroutines como sendo um tipo de [iCODE]thread[/iCODE] mais leve. Embora correta, essa afirmação pode gerar confusões.

 

Uma [iCODE]goroutine[/iCODE] é uma estrutura usada pelo runtime da qual conterá uma pilha de execução, onde o próprio [iCODE]runtime[/iCODE] pode gerenciar sua execução. Para gerenciar a execução das rotinas, o Go implementa três componentes principais, introduzidos por [iCODE]Dmitry Vyukov[/iCODE] em seu trabalho Scalable Go Scheduler Design Doc.

 

De forma resumida, são eles:

 

  • [iCODE]G (Goroutine)[/iCODE]: Contem a pilha de execução e todos artefatos necessários para execução, descrito na documentação como um tipo de [iCODE]thread[/iCODE], porém muito mais leve.
  • [iCODE]M (Thread)[/iCODE]: M é a forma mais comum de referenciar uma [iCODE]thread[/iCODE] do sistema operacional.
  • [iCODE]P (Processor)[/iCODE]: Processor é uma estrutura lógica introduzida por [iCODE]Dmitry Vyukov[/iCODE] para minimizar o número de [iCODE]goroutines[/iCODE] que ficam em estado de espera quando uma [iCODE]thread M[/iCODE] realiza uma operação bloqueante, conhecida como [iCODE]sys-call[/iCODE]. O [iCODE]Processor[/iCODE] implementa uma série de algoritmos que controlam a distribuição das rotinas entre as [iCODE]threads[/iCODE] disponíveis. Vale lembrar que existe uma relação íntima entre um [iCODE]Processor P[/iCODE] e uma [iCODE]Thread M[/iCODE], porque o [iCODE]P[/iCODE] é uma estrutura lógica e, no fim do dia, vai rodar no contexto de uma [iCODE]Thread M[/iCODE].

 

A variável de ambiente [iCODE]GOMAXPROCS[/iCODE] controla o número máximo de [iCODE]Processors[/iCODE] disponíveis para nossa aplicação. Para facilitar o entendimento deste ponto, o [iCODE]GOMAXPROCS[/iCODE] não controla o número máximo de [iCODE]Threads M[/iCODE] disponíveis para nossa aplicação. Em outras palavras, ela não inclui o número de threads bloqueadas, realizando uma chamada para o [iCODE]kernel do SO[/iCODE].

 

Em outras palavras, o [iCODE]GOMAXPROCS[/iCODE] controla o número máximo de [iCODE]threads[/iCODE] que podem ser processadas de forma concorrente pelo processador do sistema operacional. O valor padrão é o número de núcleos do processador do [iCODE]host[/iCODE] da aplicação.

 

Definir um valor muito alto para o [iCODE]GOMAXPROCS[/iCODE] pode gerar um grande número de trocas de contexto da [iCODE]CPU[/iCODE].

 

 

 

[HEADING=1]GOMAXPROCS no Kubernetes[/HEADING]

 

O [iCODE]GOMAXPROCS[/iCODE] ainda não tem uma integração nativa com os limites definidos para um pod do Kubernetes.

 

Por exemplo, um [iCODE]pod[/iCODE] com limite de 1 núcleo de [iCODE]CPU[/iCODE], rodando em um node com 64 núcleos de [iCODE]CPU[/iCODE], vai acabar tendo o valor padrão [iCODE]GOMAXPROCS=64[/iCODE].

 

 

A empresa Uber realizou uma série de estudos em relação aos comportamentos desse desencontro de configurações no repositório do Github: uber-go/automaxprocs.

 

[HEADING=1]Configurar GOMAXPROCS na definição do contêiner[/HEADING]

 

Para resolver esse problema, podemos configurar a variável nas especificações do contêiner da seguinte forma:

 

env:
- name: GOMAXPROCS
 valueFrom:
   resourceFieldRef:
     resource: limits.cpu

 

Imagine uma imagem [iCODE]docker[/iCODE], contendo a seguinte aplicação:

 

package main

import (
   "fmt"
   "runtime"
   "runtime/debug")

func main() {
   fmt.Printf("GOMAXPROCS: %d\n", runtime.GOMAXPROCS(0))
}

 

Podemos aplicar a configuração mencionada anteriormente na definição de um contêiner, como no exemplo abaixo:

 

apiVersion: v1
kind: Pod
metadata:
 name: app
spec:
 containers:
 - name: app
   image: imagem-da-aplicação    
   resources:
     limits:
       cpu: 1500m
   env:
   - name: GOMAXPROCS
     valueFrom:
       resourceFieldRef:
         resource: limits.cpu

 

O resultado que você terá, ao conferir os logs de execução deste [iCODE]pod[/iCODE] é:

 

GOMAXPROCS: 2

[HEADING=1] [/HEADING]

[HEADING=1]Conclusão[/HEADING]

 

Entender a relação entre o número de núcleos da [iCODE]CPU[/iCODE], [iCODE]threads[/iCODE] e [iCODE]goroutines[/iCODE] é essencial para elevar o nível de eficiência do seu serviço Go. Este artigo pode servir de base para que você realize e aprofunde seus estudos sobre o assunto proposto, para que possa ter uma grande consciência da estratégia que pretende utilizar, mudando ou não a configuração [iCODE]GOMAXPROCS[/iCODE].

 

Continue reading...

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...