Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Processando análise genética em background com Go

Processando análise genética em background com Go

Desde o meio do ano passado temos migrado partes de um sistema de análise genética com Go para uma versão auto contida. Um dos desafios dessa migração é como controlar os exames que devemos processar: se o processamento falhou, porque falhou, como reprocessar, retry automático, etc.

Para alguém como eu que vem do mundo Ruby, Sidekiq é quase uma unanimidade nesse quesito, mas o que tem aparecido de bom no mundo Go nos últimos tempos?

Vale a pena usar bibliotecas como GoWorker e Faktory ou devemos ir para um caminho mais híbrido com Amazon SQS, RabbitMQ, Apache Kafka e afins?

Nessa talk vamos compartilhar nossas experiências em processar mensalmente milhares de exames genéticos em Go.

Marco Antônio Singer

July 21, 2018
Tweet

More Decks by Marco Antônio Singer

Other Decks in Technology

Transcript

  1. ~ vsa vsa stands for *Very Simple Annotator*. This package

    annotates variants in VCF format Usage: vsa [command] Available Commands: dx Annotate and classify variants given a VCF file help Help about any command server Run vsa server at port 9090 train Prepare features for training or standardization Flags: -h, --help help for vsa Use "vsa [command] --help" for more information about a command.
  2. Premissas do VSA-Worker • Receber os exames que precisamos processar

    • Delegar a execução para o nosso binário • Notificar em caso de erro no processamento
  3. Tecnologias • RabbitMQ (exemplo) • Apache Kafka (canhão) • Amazon

    SQS (não funciona offline) • Sidekiq (mais uma linguagem na stack)
  4. Faktory https://github.com/contribsys/faktory • Criador do Sidekiq • Feito em Go

    • Clientes em Go e Ruby "oficiais" • Usa RocksDB para persistência • Vem com um dashboard built-in • Retry automático • Pode agendar execução de um job
  5. Rodando Faktory docker pull contribsys/faktory docker run --rm -it -p

    7419:7419 -p 7420:7420 contribsys/faktory comunicação cliente / servidor
  6. Rodando Faktory docker pull contribsys/faktory docker run --rm -it -p

    7419:7419 -p 7420:7420 contribsys/faktory porta da interface web
  7. ~ vsa dx --help Annotate and classify variants given a

    VCF file. Usage: vsa dx [flags] Flags: --assembly string Supported assemblies: GRCh37 or GRCh38. --vcf string VCF file address.
  8. Criando um job go get -u github.com/contribsys/faktory/client import ( faktory

    "github.com/contribsys/faktory/client" ) func send(){ client, err := faktory.Open() if err != nil { // tratamento de erro } defer client.Close() job := faktory.NewJob("dx", "GRCh37", "/path/to/file") if err := client.Push(job); err != nil { // tratamento de erro } }
  9. ~ vsa dx --help Annotate and classify variants given a

    VCF file. Usage: vsa dx [flags] Flags: --assembly string Supported assemblies: GRCh37 or GRCh38. --vcf string VCF file address.
  10. package worker import ( worker "github.com/contribsys/faktory_worker_go" ) func Run() {

    svc := worker.NewManager() svc.Register("dx", dxWorker) }
  11. package worker import ( worker "github.com/contribsys/faktory_worker_go" ) func Run() {

    svc := worker.NewManager() svc.Register("dx", dxWorker) } payload jobType
  12. package worker import ( worker "github.com/contribsys/faktory_worker_go" ) func Run() {

    svc := worker.NewManager() svc.Register("dx", dxWorker) } ?
  13. package worker import ( worker "github.com/contribsys/faktory_worker_go" ) func Run() {

    svc := worker.NewManager() svc.Register("dx", dxWorker) }
  14. package worker import ( worker "github.com/contribsys/faktory_worker_go" ) func Run() {

    svc := worker.NewManager() svc.Register("dx", dxWorker) svc.Run() }
  15. Premissas do VSA-Worker • Receber os exames que precisamos processar

    • Delegar a execução para o nosso binário • Notificar em caso de erro no processamento
  16. main.go package main import ( // ... ) func main()

    { // running workers in background go func() { worker.Run() }() // configure API handlers svc := api.New() http.Handle("/", svc.Handler()) logrus.WithField("port", config.Port).Info("starting server") if err := http.ListenAndServe(":"+config.Port, nil); err != nil { logrus.WithError(err).Fatalf("something went wrong") } }
  17. main.go package main import ( // ... ) func main()

    { // running workers in background go func() { worker.Run() }() // configure API handlers svc := api.New() http.Handle("/", svc.Handler()) logrus.WithField("port", config.Port).Info("starting server") if err := http.ListenAndServe(":"+config.Port, nil); err != nil { logrus.WithError(err).Fatalf("something went wrong") } }
  18. main.go package main import ( // ... ) func main()

    { // running workers in background go func() { worker.Run() }() // configure API handlers svc := api.New() http.Handle("/", svc.Handler()) logrus.WithField("port", config.Port).Info("starting server") if err := http.ListenAndServe(":"+config.Port, nil); err != nil { logrus.WithError(err).Fatalf("something went wrong") } }
  19. api.go package main import ( // ... ) func (s

    Service) Handler() *mux.Router { r := mux.NewRouter() r.PathPrefix("/api") r.HandleFunc("/v1/import", s.importHandler).Methods("POST") return r }
  20. api.go package main import ( // ... ) func (s

    Service) Handler() *mux.Router { r := mux.NewRouter() r.PathPrefix("/api") r.HandleFunc("/v1/import", s.importHandler).Methods("POST") return r }
  21. api.go type ImportInput struct { Assembly string `json:"assembly,omitempty"` Vcf string

    `json:"vcf,omitempty"` } func importHandler(w http.ResponseWriter, r *http.Request) { var input ImportInput json.NewDecoder(r.Body).Decode(&input) client, _ := faktory.Open() defer client.Close() job := faktory.NewJob("dx", input.Assembly, input.Vcf) client.Push(job) w.WriteHeader(http.StatusOK) }
  22. api.go type ImportInput struct { Assembly string `json:"assembly,omitempty"` Vcf string

    `json:"vcf,omitempty"` } func importHandler(w http.ResponseWriter, r *http.Request) { var input ImportInput json.NewDecoder(r.Body).Decode(&input) client, _ := faktory.Open() defer client.Close() job := faktory.NewJob("dx", input.Assembly, input.Vcf) client.Push(job) w.WriteHeader(http.StatusOK) }
  23. api.go type ImportInput struct { Assembly string `json:"assembly,omitempty"` Vcf string

    `json:"vcf,omitempty"` } func importHandler(w http.ResponseWriter, r *http.Request) { var input ImportInput json.NewDecoder(r.Body).Decode(&input) client, _ := faktory.Open() defer client.Close() job := faktory.NewJob("dx", input.Assembly, input.Vcf) client.Push(job) w.WriteHeader(http.StatusOK) }
  24. main.go package main import ( // ... ) func main()

    { // running workers in background go func() { worker.Run() }() // configure API handlers svc := api.New() http.Handle("/", svc.Handler()) logrus.WithField("port", config.Port).Info("starting server") if err := http.ListenAndServe(":"+config.Port, nil); err != nil { logrus.WithError(err).Fatalf("something went wrong") } }
  25. worker.go package worker import ( // ) func Run() {

    logrus.Info("starting workers in background mode") svc := worker.NewManager() logrus.WithField("jobType", "dx").Info("register job") svc.Register("dx", dxWorker) logrus.Info("waiting for jobs") svc.Run() }
  26. func dxWorker(ctx worker.Context, args ...interface{}) error { var ( assembly

    = args[0].(string) vcf = args[1].(string) ) cmd := exec.Command("vsa", "dx", "--assembly", assembly, "--vcf", vcf) stderr, err := cmd.StderrPipe() if err != nil { return err } if err := cmd.Start(); err != nil { return err } rerr, _ := ioutil.ReadAll(stderr) if err := cmd.Wait(); err != nil { return fmt.Errorf("%s", rerr) } return nil }
  27. func dxWorker(ctx worker.Context, args ...interface{}) error { var ( assembly

    = args[0].(string) vcf = args[1].(string) ) cmd := exec.Command("vsa", "dx", "--assembly", assembly, "--vcf", vcf) stderr, err := cmd.StderrPipe() if err != nil { return err } if err := cmd.Start(); err != nil { return err } rerr, _ := ioutil.ReadAll(stderr) if err := cmd.Wait(); err != nil { return fmt.Errorf("%s", rerr) } return nil }
  28. func dxWorker(ctx worker.Context, args ...interface{}) error { var ( assembly

    = args[0].(string) vcf = args[1].(string) ) cmd := exec.Command("vsa", "dx", "--assembly", assembly, "--vcf", vcf) stderr, err := cmd.StderrPipe() if err != nil { return err } if err := cmd.Start(); err != nil { return err } rerr, _ := ioutil.ReadAll(stderr) if err := cmd.Wait(); err != nil { return fmt.Errorf("%s", rerr) } return nil }
  29. func dxWorker(ctx worker.Context, args ...interface{}) error { var ( assembly

    = args[0].(string) vcf = args[1].(string) ) cmd := exec.Command("vsa", "dx", "--assembly", assembly, "--vcf", vcf) stderr, err := cmd.StderrPipe() if err != nil { return err } if err := cmd.Start(); err != nil { return err } rerr, _ := ioutil.ReadAll(stderr) if err := cmd.Wait(); err != nil { return fmt.Errorf("%s", rerr) } return nil }
  30. func dxWorker(ctx worker.Context, args ...interface{}) error { var ( assembly

    = args[0].(string) vcf = args[1].(string) ) cmd := exec.Command("vsa", "dx", "--assembly", assembly, "--vcf", vcf) stderr, err := cmd.StderrPipe() if err != nil { return err } if err := cmd.Start(); err != nil { return err } rerr, _ := ioutil.ReadAll(stderr) if err := cmd.Wait(); err != nil { return fmt.Errorf("%s", rerr) } return nil }
  31. Exemplo de call curl -XPOST \ -H "Content-Type: application/json" \

    --data @payload.json \ http://host:port/api/v1/import