Slide 1

Slide 1 text

Busca de alta performance Busca de alta performance com GraphQL e Elasticsearch com GraphQL e Elasticsearch Guilherme Baptista GopherConBR 2019 1

Slide 2

Slide 2 text

Agenda Agenda O que é performance? Qual problema queremos resolver? Como escolhemos Go? Como trabalhamos com Elasticsearch? Onde entra GraphQL na história? Resultados Re exões 2

Slide 3

Slide 3 text

O que é performance? O que é performance? 3

Slide 4

Slide 4 text

O que é performance? O que é performance? Estrangeirismo predileto na área de T.I. 4

Slide 5

Slide 5 text

Performance Performance em português: Desempenho Desempenho 5

Slide 6

Slide 6 text

Desempenho Desempenho associado com: Velocidade Velocidade 6

Slide 7

Slide 7 text

Velocidade? Velocidade? 7

Slide 8

Slide 8 text

Tempo de resposta: 1 milissegundo 1 milissegundo Servidores: 150 mil dólares por mês; Taxa de erros: 97% das requisições. 8

Slide 9

Slide 9 text

Serviço de alta Serviço de alta performance: performance: Alta velocidade; Baixa taxa de erros; Mínimo custo nanceiro; ... 9

Slide 10

Slide 10 text

Simplificando, Simplificando, vamos associar performance performance com: 10

Slide 11

Slide 11 text

Tempo de resposta Tempo de resposta "Essa página demora quantos segundos para abrir?" Throughput suportado (rpm) Throughput suportado (rpm) "Quantos acessos por minuto esse site aguenta? 11

Slide 12

Slide 12 text

Tempo de resposta Tempo de resposta Por que isso importa? 12

Slide 13

Slide 13 text

O óbvio: O óbvio: Ninguém gosta de site lento. 13

Slide 14

Slide 14 text

Sites que demoram mais que 3 segundos 3 segundos v 80% nunca voltam; 57% desistem do acesso; 50% falam mal para outras pessoas. 14

Slide 15

Slide 15 text

Para cada 100 milissegundos 100 milissegundos a mais no tempo de resposta a Amazon perde -1% de suas vendas. -1% de suas vendas. 15

Slide 16

Slide 16 text

Para cada 1 segundo 1 segundo a menos no tempo de resposta o Walmart ganha +2% de aumento em suas +2% de aumento em suas vendas. vendas. 16

Slide 17

Slide 17 text

Com uma melhoria de 40% 40% no tempo de carregamento de suas páginas o Pinterest conseguiu um aumento de +15% no cadastro de usuários. +15% no cadastro de usuários. 17

Slide 18

Slide 18 text

Para cada 1 segundo 1 segundo a mais no tempo de resposta a BBC perde -10% de seus usuários. -10% de seus usuários. 18

Slide 19

Slide 19 text

Throughput suportado Throughput suportado Por que isso importa? 19

Slide 20

Slide 20 text

Um estudo identi cou que quando sites cam fora do ar fora do ar v Queda de 100% nos acessos; Queda de 100% nas vendas; Aumento de 300% nos batimentos cardíacos de quem cuida do site. 20

Slide 21

Slide 21 text

Por onde começar então? Por onde começar então? 1968 1968 George A. Miller 21

Slide 22

Slide 22 text

Ciência cognitiva Ciência cognitiva George A. Miller Mnemônica; Lei de Miller: 7 coisas de cada vez; Palácio da memória (Sherlock Holmes); Programação neurolinguística (PNL); Psicologia e informação. 22

Slide 23

Slide 23 text

1968 1968 A interação entre seres humanos e sistemas de informação. Quais os limites para manter a atenção de seres humanos ao interagirem com sistemas de informações? 23

Slide 24

Slide 24 text

24

Slide 25

Slide 25 text

Até 100 milissegundos Até 100 milissegundos Ter a sensação de instantâneo. Até 1 segundo Até 1 segundo Ter a sensação de continuidade. 25

Slide 26

Slide 26 text

Entre 1 e 10 segundos Entre 1 e 10 segundos Manter a atenção enquanto aguarda. Acima de 10 segundos Acima de 10 segundos Atenção perdida. 26

Slide 27

Slide 27 text

1968 1968 2019? 2019? 27

Slide 28

Slide 28 text

FPS ( FPS (quadros por segundo quadros por segundo) ) Olho humano: 12 FPS 12 FPS Tela de um celular moderno: 60 FPS 60 FPS 28

Slide 29

Slide 29 text

Olho humano (12 FPS): 83 milissegundos 83 milissegundos Tela de um celular moderno (60 FPS): 16 milissegundos 16 milissegundos 29

Slide 30

Slide 30 text

Até 16 milissegundos Até 16 milissegundos Não existe diferença real para renderização. Mais que 100 milissegundos 100 milissegundos Seres humanos perdem a percepção de instantâneo. 30

Slide 31

Slide 31 text

Renderização progressiva Renderização progressiva https://developers.google.com 31

Slide 32

Slide 32 text

Diretrizes para tempo Diretrizes para tempo de resposta de resposta 32

Slide 33

Slide 33 text

API's: Idealmente, mantenha tudo abaixo de 100 milissegundos 100 milissegundos Nunca ultrapasse os 200 milissegundos 200 milissegundos 33

Slide 34

Slide 34 text

Interfaces: Idealmente, renderize tudo em até 100 milissegundos 100 milissegundos Utilize técnicas de renderização progressiva e nunca ultrapasse 1 segundo 1 segundo 34

Slide 35

Slide 35 text

Qual problema Qual problema queremos resolver? queremos resolver? 35

Slide 36

Slide 36 text

enjoei.com.br enjoei.com.br Marketplace de moda com milhões de produtos que precisa: Fazer buscas em todos esses milhões de produtos; Aguentar picos de acessos por conta de programas na TV. 36

Slide 37

Slide 37 text

O cenário O cenário Altos custos com infraestrutura; Tempo de resposta acima dos 200 milissegundos; Grande di culdade em aguentar picos de acessos instantâneos. 37

Slide 38

Slide 38 text

Características Características API's em Ruby on Rails; Muito código legado; Elasticsearch com problemas de performance. 38

Slide 39

Slide 39 text

Como escolhemos Go? Como escolhemos Go? 39

Slide 40

Slide 40 text

"Não dá mais." "Não dá mais." 40

Slide 41

Slide 41 text

O custo de reescrever tudo O custo de reescrever tudo "Se for pra mudar, tem que valer a pena." "Será que precisa mesmo?" https://roda.jeremyevans.net/ 41

Slide 42

Slide 42 text

42

Slide 43

Slide 43 text

https://www.techempower.com/benchmarks/ 43

Slide 44

Slide 44 text

E o roda? 44

Slide 45

Slide 45 text

E o roda? ... 45

Slide 46

Slide 46 text

E o roda? 46

Slide 47

Slide 47 text

"Se for pra mudar, tem que valer a pena." "Se for pra mudar, tem que valer a pena." 47

Slide 48

Slide 48 text

Ruby, até mais, e obrigado pelos peixes! ♥ 48

Slide 49

Slide 49 text

Should I Rust, or Should I Go? https://codeburst.io 49

Slide 50

Slide 50 text

Muitos testes, estudos e benchmarks depois: Let's Go! Let's Go! Sempre no top 3 de qualquer benchmark; Mais opções de bibliotecas; Curva de aprendizado menor; Maior produtividade no dia a dia. 50

Slide 51

Slide 51 text

package main import "fmt" func main() { fmt.Println("hello world") } 51

Slide 52

Slide 52 text

E agora? E agora? 52

Slide 53

Slide 53 text

Como servir dados via HTTP? Como servir dados via HTTP? Sem reinventar a roda ou fazer tudo na mão. Equilíbrio entre alta performance e usabilidade. Nossa escolha: https://github.com/go-chi/chi 53

Slide 54

Slide 54 text

router := chi.NewRouter() router.Use( middleware.Logger, render.SetContentType(render.ContentTypeJSON), ) router.Get("/status", statusHandler) func statusHandler(w http.ResponseWriter, r *http.Request) { w.Write([]byte("ok")) } http.ListenAndServe(":8484", router) 54

Slide 55

Slide 55 text

Primeira surpresa Primeira surpresa 55

Slide 56

Slide 56 text

9,991µs 9,991µs µs µs 56

Slide 57

Slide 57 text

µs µs signi ca microssegundos microssegundos 57

Slide 58

Slide 58 text

1 segundo 1 segundo é igual a 1 mil milissegundos 1 mil milissegundos que é igual a 1 milhão de microssegundos 1 milhão de microssegundos 58

Slide 59

Slide 59 text

9,991µs 9,991µs é igual a 0,000009991 segundos 0,000009991 segundos 59

Slide 60

Slide 60 text

Como trabalhamos Como trabalhamos com Elasticsearch? com Elasticsearch? 60

Slide 61

Slide 61 text

Do jeito errado. Do jeito errado. 61

Slide 62

Slide 62 text

O que aprendemos: O que aprendemos: É a melhor opção para buscas complexas em um volume enorme de dados. É tão simples e prático que mesmo fazendo tudo errado ele funciona. A modelagem dos seus documentos é tudo. 62

Slide 63

Slide 63 text

O que aprendemos: O que aprendemos: Não tentar usar o mesmo índice para responder perguntas diferentes. "keyword" ao invés de "text" faz toda diferença. Scripts são malignos. Arredondar datas economiza muito dinheiro. 63

Slide 64

Slide 64 text

Como ler dados do Como ler dados do Elasticsearch com Go? Elasticsearch com Go? Nossa escolha: https://github.com/olivere/elastic 64

Slide 65

Slide 65 text

client, _ := elastic.NewClient( elastic.SetURL("http://127.0.0.1:9201")) searchResult, _ := client.Search(). Index("products"). Query(elastic.NewTermQuery("title", "sandália preta")). Do(context.Background()) var product Product for _, p := range searchResult.Each(reflect.TypeOf(product)) { product := p.(Product) fmt.Printf("%s", product.Title) } 65

Slide 66

Slide 66 text

O primeiro problema... O primeiro problema... "Já ví esse lme antes..." 66

Slide 67

Slide 67 text

Elasticsearch Query DSL Elasticsearch Query DSL Uma lição que aprendemos: Toda biblioteca que tenta criar uma abstração para Elasticsearch resulta em código que ninguém entende. 67

Slide 68

Slide 68 text

Funciona bem para SQL Funciona bem para SQL 68

Slide 69

Slide 69 text

User.where(name: 'David', occupation: 'Code Artist') .order(created_at: :desc) User.where(email: '[email protected]').first User.where('update_at > ?', 1989).each do |user| puts user.name end 69

Slide 70

Slide 70 text

user := &User{} db.Model(user) .Where("name", "David").Where("occupation", "Code Artist") .Order("created_at DESC").Select() db.Model(user).Where("email", "[email protected]").Select() var users []User db.Model(&users).Where('update_at > ?', 1989).Select() for _, user := range users { fmt.Printf("%s", user.Name) } 70

Slide 71

Slide 71 text

Não funciona bem para Não funciona bem para Elasticsearch Elasticsearch 71

Slide 72

Slide 72 text

query do script_fields distance: { script: "doc['location'].distance(lat, lon)", params: {lat: latitude, lon:longitude} } fields ['_source'] query do filtered do query do multi_match do query str fields %w[title name] end 72

Slide 73

Slide 73 text

query := elastic.NewMatchAllQuery() aggs := elastic.NewDateHistogramAggregation(). Field("time"). Interval("hour"). Format("yyyy-MM-dd HH") aggs.SubAggregation( "count", elastic.NewSumAggregation().Field("no_of_hits")) searchResult, _ := client.Search(). Index("my_index"). Type("_doc"). Query(query). Aggregation("date", aggs). Do(ctx) hour_agg, found := searchResult.Aggregations.Terms("date") 73

Slide 74

Slide 74 text

Qual a solução então? Qual a solução então? 74

Slide 75

Slide 75 text

Templates JSON Templates JSON 75

Slide 76

Slide 76 text

{ "size": 10, "query": { "bool": { "must": { "match": { "title": "vestido" } }, "filter": { { "term": { "color": "vermelho" } } } } } } 76

Slide 77

Slide 77 text

Qual engine de templates? Qual engine de templates? Equilíbrio entre alta performance e usabilidade. Nossa escolha: https://github.com/shiyanhui/hero 77

Slide 78

Slide 78 text

78

Slide 79

Slide 79 text

79

Slide 80

Slide 80 text

<%: func SearchProducts(term string, color string) %> { "size": 10, "query": { "bool": { "must": { "match": { "title": "<%==s term %>" } }, "filter": { { "term": { "color": "<%==s color %>" } } } } } } 80

Slide 81

Slide 81 text

func Search() { jsonBody := new(bytes.Buffer) templates.SearchProducts("vestido", "vermelho", jsonBody) products := elastic.RequestFromJson( "GET", "/products/_search", jsonBody) } 81

Slide 82

Slide 82 text

Precisa da biblioteca para Elasticsearch então? Precisa da biblioteca para Elasticsearch então? Sim Sim Controle de conexões com o Elasticsearch; Autenticação do cliente; Parser de resultados; Bulk API para inserção de dados. https://github.com/olivere/elastic 82

Slide 83

Slide 83 text

Onde entra GraphQL Onde entra GraphQL na história? na história? 83

Slide 84

Slide 84 text

Como funciona conceitualmente Como funciona conceitualmente 84

Slide 85

Slide 85 text

Teoria dos grafos Teoria dos grafos 85

Slide 86

Slide 86 text

Representação de recursos Representação de recursos 86

Slide 87

Slide 87 text

Um produto e o seu vendedor Um produto e o seu vendedor 87

Slide 88

Slide 88 text

product(id: 8945) { title seller { name } } 88

Slide 89

Slide 89 text

query productQuery($id: ID!) { product(id: $id) { title seller { name } } } } { "id": 8945 } 89

Slide 90

Slide 90 text

{ "data": { "product": { "title": "vestido vermelho", "seller": { "name": "beatriz" } } } } 90

Slide 91

Slide 91 text

Paginação Paginação 91

Slide 92

Slide 92 text

Paginação baseada em Offset Paginação baseada em Offset 92

Slide 93

Slide 93 text

Esqueça paginação Esqueça paginação baseada em Offset baseada em Offset 93

Slide 94

Slide 94 text

Paginação baseada em Cursores: Paginação baseada em Cursores: 94

Slide 95

Slide 95 text

Metadados nas conexões e arestas Metadados nas conexões e arestas 95

Slide 96

Slide 96 text

Como esses dados são Como esses dados são representados? representados? 96

Slide 97

Slide 97 text

Representação simplifcada: { "product": { "title": "vestido vermelho", "seller": { "name": "lojinha da bia" } } } 97

Slide 98

Slide 98 text

Representação com conexões e arestas: { "user": { "productConnection": { "totalCount": 37, "productEdges": [ { "node": { "title": "vestido vermelho" }, "cursor": "726" } ] } } } 98

Slide 99

Slide 99 text

Como paginar os dados: query userProducts($user_id: ID!, $first: Int, $after: ID) { user(id: $user_id) { products(first: $first, after: $after) { totalCount edges { node { title } cursor } } } } { "user_id": 1873, "first": 10, "after": "726" } 99

Slide 100

Slide 100 text

A estrutura de uma busca A estrutura de uma busca 100

Slide 101

Slide 101 text

Como uma busca é feita: query Search($search: Search, $first: Int, $after: ID) { search(search: $search) { term products(first: $first, after: $after) { totalCount edges { node { title seller { name } } cursor } } } } { "search": { "term": "vestido vermelho" }, "first": 10, "after": "236" } 101

Slide 102

Slide 102 text

O resultado: "search": { "term": "vestido vermelho", "products": { "totalCount": 152836, "edges": [ { "node": { "title": "vestido vermelho", "seller": { "name": "beatriz" } }, "cursor": "245" } ] } } 102

Slide 103

Slide 103 text

Fragmentos Fragmentos 103

Slide 104

Slide 104 text

fragment productWithSeller on Product { title seller { name } } 104

Slide 105

Slide 105 text

search(search: $search) { products(first: $first, after: $after) { edges { node { ...productWithSeller } } } } product(id: $id) { ...productWithSeller } 105

Slide 106

Slide 106 text

Mas por que tudo isso Mas por que tudo isso importa? importa? 106

Slide 107

Slide 107 text

Em uma interface Em uma interface 107

Slide 108

Slide 108 text

Diversas requisições diferentes Diversas requisições diferentes GET /current_user GET /product/7 GET /product/7/seller GET /product/7/comment/4 108

Slide 109

Slide 109 text

440 milissegundos 440 milissegundos em 1 requisição 109

Slide 110

Slide 110 text

Agora imagine fazer isso... Agora imagine fazer isso... 4 vezes; 4 vezes; Servidor nos EUA; Servidor nos EUA; Celular com 3G. Celular com 3G. 110

Slide 111

Slide 111 text

100 milissegundos 100 milissegundos Sensação de instantâneo. Sensação de instantâneo. 111

Slide 112

Slide 112 text

1 requisição para resolver tudo: currentUser { name gender } product { title price seller { name } comments { author { name } message replies { author { name } message } } } 112

Slide 113

Slide 113 text

GraphQL + Go GraphQL + Go 113

Slide 114

Slide 114 text

Goroutines Goroutines 114

Slide 115

Slide 115 text

var waitGroup sync.WaitGroup user := &User{} product := &Product{} waitGroup.Add(1) go loadUser(&waitGroup, user) waitGroup.Add(1) go loadProduct(&waitGroup, product) waitGroup.Wait() 115

Slide 116

Slide 116 text

Como servir dados via Como servir dados via GraphQL? GraphQL? Equilíbrio entre alta performance e usabilidade. Nossa primeira tentativa: https://github.com/graphql-go/graphql 116

Slide 117

Slide 117 text

graphql.NewObject( graphql.ObjectConfig{ Name: "GraphQL", Fields: fields}) graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "source": &graphql.Field{Type: graphql.String}}}) graphql.NewSchema(graphql.SchemaConfig{Query: queryType}) var query_args = graphql.FieldConfigArgument{ "term": &graphql.ArgumentConfig{Type: graphql.String}, var Query = &graphql.Field{ Type: gq.Query, Args: query_args, Resolve: resolvers.Query,} 117

Slide 118

Slide 118 text

Verboso; Verboso; Complexo; Complexo; Confuso; Confuso; Nada produtivo. Nada produtivo. 118

Slide 119

Slide 119 text

Schema based Schema based 119

Slide 120

Slide 120 text

type Query { user(id: ID!): User product(id: ID!): Product } type User { id: ID name: String } type Product { id: ID title: String seller: User } 120

Slide 121

Slide 121 text

Nossa segunda tentativa: https://github.com/99designs/gqlgen 121

Slide 122

Slide 122 text

Geração de código; Geração de código; Funcionamento obscuro; Funcionamento obscuro; Muita mágica. Muita mágica. 122

Slide 123

Slide 123 text

Nossa escolha nal: https://github.com/graph-gophers/graphql-go 123

Slide 124

Slide 124 text

type Query { user(id: ID!): User product(id: ID!): Product } type User { id: ID name: String } type Product { id: ID title: String seller: User } 124

Slide 125

Slide 125 text

type UserResolver struct { Field fields.Field `graphql:"user"` Model models.User } func (resolver *Resolver) User(id params.Id) *UserResolver { return &UserResolver{Model: userModel.Find(id)} } func (userResolver *UserResolver) Name() *string { return &userResolver.Model.Name } 125

Slide 126

Slide 126 text

Simples; Simples; Direto ao ponto; Direto ao ponto; Paralelismo por padrão. Paralelismo por padrão. 126

Slide 127

Slide 127 text

Paralelismo por padrão: user { name } product { title comments { message author { name } } } 127

Slide 128

Slide 128 text

128

Slide 129

Slide 129 text

Tem como melhorar? Tem como melhorar? Sim Sim 129

Slide 130

Slide 130 text

requested-fields requested-fields https://github.com/gbaptista/requested- elds 130

Slide 131

Slide 131 text

hasComments := fields.RequestedForAt(ctx, productResolver, "comments") hasCommentAuthor := fields.RequestedForAt(ctx, productResolver, "comments.author") if hasComments { waitGroup.Add(1) go loadCommentsFor(productResolver.Model) } if hasCommentAuthor { waitGroup.Add(1) go loadCommentAuthorsFor(productResolver.Model) } 131

Slide 132

Slide 132 text

132

Slide 133

Slide 133 text

Tem como melhorar? Tem como melhorar? Sim Sim 133

Slide 134

Slide 134 text

134

Slide 135

Slide 135 text

Resultados Resultados 135

Slide 136

Slide 136 text

Throughput suportado (rpm) Throughput suportado (rpm) 136

Slide 137

Slide 137 text

Tempo de resposta Tempo de resposta 137

Slide 138

Slide 138 text

Servidores Servidores 138

Slide 139

Slide 139 text

Elasticserach Elasticserach 139

Slide 140

Slide 140 text

Reflexões Reflexões 140

Slide 141

Slide 141 text

Não se apegue a Não se apegue a tecnologias. tecnologias. 141

Slide 142

Slide 142 text

Ruby é incrível, mas não Ruby é incrível, mas não precisa resolver tudo. precisa resolver tudo. Go é só uma das Go é só uma das opções. opções. 142

Slide 143

Slide 143 text

Os problemas de Os problemas de amanhã serão maiores amanhã serão maiores que os de hoje. que os de hoje. Rust está vindo com tudo. Rust está vindo com tudo. 143

Slide 144

Slide 144 text

Sempre tem como Sempre tem como melhorar. melhorar. 144

Slide 145

Slide 145 text

Ainda podemos: Ainda podemos: Usar HTTP2 ao invés de HTTP1; Usar HTTP2 ao invés de HTTP1; Usar gRPC como protocolo de comunicação; Usar gRPC como protocolo de comunicação; Criar estratégias agressivas de cache; Criar estratégias agressivas de cache; Trabalhar com WebAssembly no navegador. Trabalhar com WebAssembly no navegador. 145

Slide 146

Slide 146 text

Não vai ficar perfeito. Não vai ficar perfeito. E tudo bem. 146

Slide 147

Slide 147 text

query( $products: ProductSearch, $first: Int, $after: ID ) { search(search: $products) { term products(first: $first, after: $after) { edges { node { title } cursor } } } } 147

Slide 148

Slide 148 text

query( $products: ProductSearch, $users: UserSearch, $first: Int, $after: ID ) { search(products: $products, users: $users) { term products(first: $first, after: $after) { edges { node { title } cursor } } users(first: $first, after: $after) { edges { node { name } cursor } } } } 148

Slide 149

Slide 149 text

Estudar novas linguagens abre Estudar novas linguagens abre a sua mente e acaba sendo a sua mente e acaba sendo divertido. divertido. Não tenha medo. 149

Slide 150

Slide 150 text

150

Slide 151

Slide 151 text

Obrigado! Obrigado! https://github.com/gbaptista https://twitter.com/gbaptistasss https://www.linkedin.com/in/guilhermebaptista 151