Processando milhões de mensagens com Python e Kubernetes

BIANCA ROSA

Vamos falar sobre...

  • Arquiteturas Escaláveis
  • O nosso problema
  • A nossa solução
  • Dificuldades

Arquiteturas Escaláveis

Nasce um sistema!

E cresce um sistema!

Quando vamos ver...

O problema do forte acoplamento

  • Lentidão pra processar uma ação
  • Podem pertencer a times diferentes
  • O que acontece se algum deles falha?

Evite arquiteturas com forte acoplamento.

Elas não escalam.

Ganhando escalabilidade + tolerância a falha

O nosso problema

8MM mensagens / dia
Disponibilidade em Tempo Real
MVP disponível rápido

Informação disponível no EventHub
Contrato podia mudar

A nossa solução

Python
+

Firestore

+

Azure Blob Storage

+

Kubernetes

Python

  • Tempo curto
  • Contrato podia mudar
  • Linguagem que o time dominava em comum

Firestore

  • Contrato podia mudar - NoSQL
  • Banco gerenciado (NoOps)
  • Aplicação iria ficar hospedada dentro do GCP
  • Dá pra brincar com WebSocket

Azure Blob Storage

  • EventHub exige que a gente faça controle de offset
  • Lib pronta da Microsoft
  • Não tinha tempo pra escrever uma lib que funcionasse com uma solução no GCP

Kubernetes

  • Fácil pra escalar o consumo manualmente
  • Alta disponibilidade e confiança
  • Autoscaling configurável
  • Extensível pra chegar numa solução robusta
FROM python:3.7

RUN pip install pipenv

COPY . /app
WORKDIR /app

RUN pipenv install --system --deploy --ignore-pipfile

CMD ["make", "run"]
-include .env
export

run:
	newrelic-admin run-program python main.py	

test:
	pytest tests/unit

integration-test:
	pytest tests/integration

coverage:
	pytest tests/unit --doctest-modules --junitxml=junit/test-results.xml --cov=app --cov-report=xml --cov-report=html

lint:
	pylint app
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: my-python-app-consumer
spec:
  replicas: 8
  template:
    metadata:
      labels:
        app: my-python-app-consumer
    spec:
      containers:
        - name: my-python-app-consumer
          image: MY_PYTHON_APP_IMAGE
          envFrom:
            - configMapRef:
                name: my-python-app-consumer-env
            - secretRef:
                name: my-python-app-consumer-secrets
apiVersion: v1
kind: ConfigMap
metadata:
  name: client-realtime-transactional-consumer-env
data:
  EVENT_HUB_NAME: $(EVENT_HUB_NAME)
  EVENT_HUB_NAMESPACE: $(EVENT_HUB_NAMESPACE)
  EVENT_HUB_USER: $(EVENT_HUB_USER)
  STORAGE_ACCOUNT_NAME: $(STORAGE_ACCOUNT_NAME)
  STORAGE_LEASE_CONTAINER_NAME: $(STORAGE_LEASE_CONTAINER_NAME)
  CHECK_MERCHANTS_COLLECTION: $(CHECK_MERCHANTS_COLLECTION)
  LOG_LEVEL: $(LOG_LEVEL)
  EVENT_HUB_COOLDOWN_IN_SECONDS: $(EVENT_HUB_COOLDOWN_IN_SECONDS)
  EVENT_HUB_RELEASE_PUMP_ON_TIMEOUT: $(EVENT_HUB_RELEASE_PUMP_ON_TIMEOUT)
  EVENT_HUB_MAX_BATCH_SIZE: $(EVENT_HUB_MAX_BATCH_SIZE)
  EVENT_HUB_PREFETCH: $(EVENT_HUB_PREFETCH)
  EVENT_HUB_CONSUMER_GROUP: $(EVENT_HUB_CONSUMER_GROUP)
  NEW_RELIC_LICENSE_KEY: $(NEW_RELIC_LICENSE_KEY)
  NEW_RELIC_MONITOR_MODE: $(NEW_RELIC_MONITOR_MODE)
  NEW_RELIC_APP_NAME: $(NEW_RELIC_APP_NAME)
apiVersion: v1
kind: Secret
metadata:
  name: client-realtime-transactional-consumer-secrets
type: Opaque
stringData:
  EVENT_HUB_KEY: $(EVENT_HUB_KEY)
  STORAGE_KEY: $(STORAGE_KEY)
name: "MyPythonApp-CI"

trigger:
  - master
  - develop

pool:
  vmImage: "ubuntu-latest"

variables:
  DockerImageName: "$(GoogleProjectID)/my-python-app-consumer-app"

steps:
  - task: UsePythonVersion@0
    displayName: "Use Python 3.7"
    inputs:
      versionSpec: "3.7"
      architecture: "x64"

  - script: pip install pipenv
    displayName: "Install pipenv"

  - script: pipenv install --system --dev --deploy --ignore-pipfile
    displayName: "Install dependencies"

  - script: make coverage
    displayName: "Run tests"

  - script: make lint
    displayName: "Run lint"

  - task: PublishTestResults@2
    condition: succeededOrFailed()
    inputs:
      testResultsFiles: "**/test-*.xml"
      testRunTitle: "Publish test results for Python 3.7"

  - task: PublishCodeCoverageResults@1
    inputs:
      codeCoverageTool: Cobertura
      summaryFileLocation: "$(System.DefaultWorkingDirectory)/**/coverage.xml"
      reportDirectory: "$(System.DefaultWorkingDirectory)/**/htmlcov"

  - task: CopyFiles@2
    inputs:
      sourceFolder: "$(System.DefaultWorkingDirectory)"
      contents: "kubernetes/*"
      targetFolder: "$(Build.ArtifactStagingDirectory)"

  - task: CmdLine@1
    displayName: "Lock image version in deployment.yaml and copy file"
    inputs:
      filename: /bin/bash
      arguments: '-c "awk ''{gsub(\"MY_PYTHON_APP_CONSUMER_IMAGE\", \"gcr.io/$(DockerImageName):$(Build.BuildId)\", $0); print}'' kubernetes/deployment.yaml > $(Build.ArtifactStagingDirectory)/kubernetes/deployment.yaml"'

  - task: PublishBuildArtifacts@1
    displayName: "Publish Artifact"
    inputs:
      PathtoPublish: "$(Build.ArtifactStagingDirectory)"

  - task: Docker@0
    displayName: "Build image"
    inputs:
      containerregistrytype: "Container Registry"
      dockerRegistryConnection: "GCR - MyPythonApp-NonPRD"
      imageName: "$(DockerImageName):$(Build.BuildId)"

  - task: Docker@0
    displayName: "Publish image"
    inputs:
      containerregistrytype: "Container Registry"
      dockerRegistryConnection: "GCR - MyPythonApp-NonPRD"
      action: "Push an image"
      imageName: "$(DockerImageName):$(Build.BuildId)"

UFA!

Entregamos!

Dificuldades

Não estava realtime

O nosso gráfico de escrita no banco estava assim...

Instrumentando o código

No detalhe...

Batch de 100 msgs
Avg de 46.2 msgs inseridas / batch
Avg time de 5000ms por batch

IT'S TEN TIMES FASTER

Gráfico pós-deploy

No NewRelic...

E um dia normal...

Temos vagas ;)