Pular para o conteúdo principal

Benchmark

Este guia documenta a configuração completa e os resultados do benchmark de acesso ao PostgreSQL RDS a partir de 10 regiões globais do Fly.io através da rede overlay WireGuard.

Visão Geral​

O benchmark mede latências de INSERT e SELECT dos nós edge do Fly.io para o AWS RDS PostgreSQL na Irlanda (eu-west-1), roteando através de um hub WireGuard no EC2.

Arquitetura do Benchmark RDS

Resultados do Benchmark​

BandeiraRegiãoLocalizaçãoInsert (ms)Read (ms)Rank
🇬🇧lhrLondres25.3911.891
🇫🇷cdgParis37.6118.482
🇩🇪fraFrankfurt54.1726.693
🇺🇸iadVirginia173.1686.104
🇺🇸ordChicago244.20121.685
🇺🇸laxLos Angeles285.52138.196
🇸🇬sinSingapura332.65165.967
🇧🇷gruSão Paulo396.09197.728
🇯🇵nrtTóquio523.35261.369
🇦🇺sydSydney538.58268.8510

Resultados Visuais​

Resultados do Benchmark RDS

Principais Conclusões​

  • Londres (LHR) tem a melhor latência (~25ms INSERT, ~12ms READ) - mais próximo do RDS na Irlanda
  • Regiões europeias (LHR, CDG, FRA) dominam o top 3 devido à proximidade geográfica
  • Costa Leste dos EUA (IAD) ~173ms - atravessando o Atlântico
  • Regiões APAC (NRT, SYD) têm as maiores latências (~520-540ms) - distância geográfica máxima

Passo 1: Configuração do Backend Fly.io​

1.1 Criar o Backend em Go​

Crie fly-backend/main.go:

package main

import (
"database/sql"
"encoding/json"
"fmt"
"net/http"
"os"
"strconv"
"time"

_ "github.com/lib/pq"
)

var (
region string
hostname string
db *sql.DB
)

func main() {
region = os.Getenv("FLY_REGION")
if region == "" {
region = "local"
}

hostname = os.Getenv("FLY_ALLOC_ID")
if hostname == "" {
hostname, _ = os.Hostname()
}
if len(hostname) > 8 {
hostname = hostname[:8]
}

port := os.Getenv("PORT")
if port == "" {
port = "8080"
}

// Inicializar banco de dados
initDB()

// Endpoints do benchmark RDS
http.HandleFunc("/api/rds/benchmark", handleRDSBenchmark)
http.HandleFunc("/api/rds/health", handleRDSHealth)
http.HandleFunc("/api/info", handleInfo)

fmt.Printf("Backend rodando na região [%s] na porta %s\n", region, port)
http.ListenAndServe(":"+port, nil)
}

func getEnv(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}

func initDB() {
dbHost := getEnv("DB_HOST", "")
if dbHost == "" {
fmt.Println("DB_HOST não configurado, benchmark RDS desabilitado")
return
}

dbPort := getEnv("DB_PORT", "5432")
dbUser := getEnv("DB_USER", "postgres")
dbPassword := getEnv("DB_PASSWORD", "")
dbName := getEnv("DB_NAME", "contacts")

connStr := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
dbHost, dbPort, dbUser, dbPassword, dbName)

var err error
db, err = sql.Open("postgres", connStr)
if err != nil {
fmt.Printf("Falha ao abrir banco de dados: %v\n", err)
return
}

db.SetMaxOpenConns(10)
db.SetMaxIdleConns(5)
db.SetConnMaxLifetime(time.Minute * 5)

if err := db.Ping(); err != nil {
fmt.Printf("Falha ao pingar banco de dados: %v\n", err)
db = nil
return
}

fmt.Printf("Banco de dados conectado: %s\n", dbHost)
}

func handleRDSBenchmark(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Header().Set("X-Fly-Region", region)

dbHost := getEnv("DB_HOST", "não configurado")

if db == nil {
json.NewEncoder(w).Encode(map[string]interface{}{
"error": "Banco de dados não configurado",
"region": region,
"db_host": dbHost,
})
return
}

iterations := 10
if iter := r.URL.Query().Get("iterations"); iter != "" {
if n, err := strconv.Atoi(iter); err == nil && n > 0 && n <= 100 {
iterations = n
}
}

readLatencies := make([]float64, iterations)
insertLatencies := make([]float64, iterations)

// Executar benchmarks de leitura (SELECT COUNT)
for i := 0; i < iterations; i++ {
start := time.Now()
var count int
db.QueryRow("SELECT COUNT(*) FROM contacts").Scan(&count)
readLatencies[i] = float64(time.Since(start).Microseconds()) / 1000.0
}

// Executar benchmarks de inserção
for i := 0; i < iterations; i++ {
start := time.Now()
name := fmt.Sprintf("Bench-%s-%d-%d", region, time.Now().UnixNano(), i)
email := fmt.Sprintf("bench-%d@test.local", time.Now().UnixNano())
db.Exec(`INSERT INTO contacts (name, email, notes) VALUES ($1, $2, $3)`,
name, email, "Benchmark")
insertLatencies[i] = float64(time.Since(start).Microseconds()) / 1000.0
}

// Calcular estatísticas
calcStats := func(latencies []float64) (avg, min, max float64) {
if len(latencies) == 0 {
return 0, 0, 0
}
min = latencies[0]
max = latencies[0]
var sum float64
for _, l := range latencies {
sum += l
if l < min {
min = l
}
if l > max {
max = l
}
}
avg = sum / float64(len(latencies))
return
}

readAvg, readMin, readMax := calcStats(readLatencies)
insertAvg, insertMin, insertMax := calcStats(insertLatencies)

result := map[string]interface{}{
"region": region,
"db_host": dbHost,
"iterations": iterations,
"read_avg_ms": readAvg,
"read_min_ms": readMin,
"read_max_ms": readMax,
"insert_avg_ms": insertAvg,
"insert_min_ms": insertMin,
"insert_max_ms": insertMax,
"read_latencies": readLatencies,
"insert_latencies": insertLatencies,
"timestamp": time.Now().UTC().Format(time.RFC3339),
}

json.NewEncoder(w).Encode(result)
}

func handleRDSHealth(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")

result := map[string]interface{}{
"region": region,
"db_host": getEnv("DB_HOST", "não configurado"),
}

if db == nil {
result["status"] = "desabilitado"
} else if err := db.Ping(); err != nil {
result["status"] = "erro"
result["message"] = err.Error()
} else {
result["status"] = "conectado"
}

json.NewEncoder(w).Encode(result)
}

func handleInfo(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")

json.NewEncoder(w).Encode(map[string]interface{}{
"region": region,
"hostname": hostname,
})
}

1.2 Criar go.mod​

module fly-backend

go 1.21

require github.com/lib/pq v1.10.9

1.3 Criar Dockerfile​

FROM golang:1.21-alpine AS builder

WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY main.go .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o backend main.go

FROM alpine:3.19
RUN apk --no-cache add ca-certificates wireguard-tools iptables ip6tables iproute2 bash
WORKDIR /app
COPY --from=builder /app/backend .
COPY entrypoint.sh .
RUN chmod +x entrypoint.sh
CMD ["./entrypoint.sh"]

1.4 Criar entrypoint.sh (WireGuard + Backend)​

#!/bin/bash
set -e

echo "=== Iniciando WireGuard + Backend ==="
echo "FLY_REGION: ${FLY_REGION}"

# Endpoint e chave pública do EC2 (hub)
EC2_ENDPOINT="54.171.48.207:51820"
EC2_PUBKEY="bzM6rw/efq+75VGhBgkCRChDnKfFlXQY560ejhvKCQY="

# Mapear região para IP WireGuard e chave privada
case "${FLY_REGION}" in
gru)
WG_IP="10.50.1.1/32"
WG_PRIVATE="SUA_CHAVE_PRIVADA_GRU"
;;
iad)
WG_IP="10.50.2.1/32"
WG_PRIVATE="SUA_CHAVE_PRIVADA_IAD"
;;
ord)
WG_IP="10.50.2.2/32"
WG_PRIVATE="SUA_CHAVE_PRIVADA_ORD"
;;
lax)
WG_IP="10.50.2.3/32"
WG_PRIVATE="SUA_CHAVE_PRIVADA_LAX"
;;
lhr)
WG_IP="10.50.3.1/32"
WG_PRIVATE="SUA_CHAVE_PRIVADA_LHR"
;;
fra)
WG_IP="10.50.3.2/32"
WG_PRIVATE="SUA_CHAVE_PRIVADA_FRA"
;;
cdg)
WG_IP="10.50.3.3/32"
WG_PRIVATE="SUA_CHAVE_PRIVADA_CDG"
;;
nrt)
WG_IP="10.50.4.1/32"
WG_PRIVATE="SUA_CHAVE_PRIVADA_NRT"
;;
sin)
WG_IP="10.50.4.2/32"
WG_PRIVATE="SUA_CHAVE_PRIVADA_SIN"
;;
syd)
WG_IP="10.50.4.3/32"
WG_PRIVATE="SUA_CHAVE_PRIVADA_SYD"
;;
*)
echo "Região desconhecida: ${FLY_REGION}, pulando WireGuard"
exec ./backend
;;
esac

echo "Configurando WireGuard com IP: ${WG_IP}"

# Criar configuração WireGuard
mkdir -p /etc/wireguard

cat > /etc/wireguard/wg0.conf << WGEOF
[Interface]
PrivateKey = ${WG_PRIVATE}
Address = ${WG_IP}

[Peer]
# EC2 Irlanda (hub)
PublicKey = ${EC2_PUBKEY}
Endpoint = ${EC2_ENDPOINT}
AllowedIPs = 10.50.0.0/24, 10.50.1.0/24, 10.50.2.0/24, 10.50.3.0/24, 10.50.4.0/24
PersistentKeepalive = 25
WGEOF

# Iniciar WireGuard
echo "Iniciando interface WireGuard..."
wg-quick up wg0 || echo "WireGuard falhou (pode precisar da capability NET_ADMIN)"

# Mostrar status
wg show || true

echo "Iniciando servidor backend..."
exec ./backend

1.5 Criar fly.toml​

app = 'edgeproxy-backend'
primary_region = 'gru'

[build]

[http_service]
internal_port = 8080
force_https = true
auto_stop_machines = 'stop'
auto_start_machines = true
min_machines_running = 0
processes = ['app']

[[vm]]
memory = '256mb'
cpu_kind = 'shared'
cpus = 1

Passo 2: Deploy no Fly.io​

2.1 Criar o app​

fly apps create edgeproxy-backend

2.2 Configurar secrets do banco de dados​

fly secrets set \
DB_HOST=10.50.0.1 \
DB_PORT=5432 \
DB_USER=contacts_user \
DB_PASSWORD=sua_senha \
DB_NAME=contacts \
-a edgeproxy-backend

2.3 Deploy em todas as regiões​

# Fazer deploy do app
fly deploy

# Escalar para todas as 10 regiões
fly scale count 1 --region gru,iad,ord,lax,lhr,fra,cdg,nrt,sin,syd -a edgeproxy-backend

Passo 3: Configuração do AWS RDS​

3.1 Criar Instância RDS​

aws rds create-db-instance \
--db-instance-identifier edgeproxy-db \
--db-instance-class db.t3.micro \
--engine postgres \
--engine-version 15.4 \
--master-username postgres \
--master-user-password SUA_SENHA \
--allocated-storage 20 \
--vpc-security-group-ids sg-xxxxxxxx \
--availability-zone eu-west-1a \
--publicly-accessible \
--no-multi-az

3.2 Desabilitar Requisito de SSL​

Criar um grupo de parâmetros personalizado:

aws rds create-db-parameter-group \
--db-parameter-group-name edgeproxy-nossl \
--db-parameter-group-family postgres15 \
--description "Desabilitar SSL para conexões WireGuard"

aws rds modify-db-parameter-group \
--db-parameter-group-name edgeproxy-nossl \
--parameters "ParameterName=rds.force_ssl,ParameterValue=0,ApplyMethod=pending-reboot"

aws rds modify-db-instance \
--db-instance-identifier edgeproxy-db \
--db-parameter-group-name edgeproxy-nossl \
--apply-immediately

aws rds reboot-db-instance --db-instance-identifier edgeproxy-db

3.3 Criar Banco de Dados e Tabela​

CREATE DATABASE contacts;

\c contacts

CREATE TABLE contacts (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255),
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE USER contacts_user WITH PASSWORD 'sua_senha';
GRANT ALL PRIVILEGES ON DATABASE contacts TO contacts_user;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO contacts_user;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO contacts_user;

Passo 4: Configuração do Hub WireGuard no EC2​

4.1 User Data do EC2 (cloud-init)​

#!/bin/bash
set -e

# Instalar WireGuard
apt-get update && apt-get install -y wireguard

# Habilitar IP forwarding
echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
sysctl -p

# Gerar chaves WireGuard
wg genkey | tee /etc/wireguard/privatekey | wg pubkey > /etc/wireguard/publickey
PRIVATE_KEY=$(cat /etc/wireguard/privatekey)

# Criar configuração WireGuard
cat > /etc/wireguard/wg0.conf << 'EOF'
[Interface]
PrivateKey = PRIVATE_KEY_AQUI
Address = 10.50.0.1/24
ListenPort = 51820
PostUp = iptables -t nat -A POSTROUTING -o ens5 -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -o ens5 -j MASQUERADE

# Peers do Fly.io (adicionar após gerar as chaves deles)
[Peer]
# fly-gru-1
PublicKey = FLY_GRU_PUBKEY
AllowedIPs = 10.50.1.1/32

[Peer]
# fly-iad-1
PublicKey = FLY_IAD_PUBKEY
AllowedIPs = 10.50.2.1/32

# ... adicionar todas as 10 regiões
EOF

# Substituir placeholder
sed -i "s|PRIVATE_KEY_AQUI|$PRIVATE_KEY|" /etc/wireguard/wg0.conf

# Iniciar WireGuard
systemctl enable wg-quick@wg0
systemctl start wg-quick@wg0

# DNAT para acesso ao RDS (rotear 10.50.0.1:5432 para o RDS)
RDS_IP="172.31.x.x" # IP privado do seu RDS
iptables -t nat -A PREROUTING -d 10.50.0.1 -p tcp --dport 5432 -j DNAT --to-destination $RDS_IP:5432
iptables -t nat -A POSTROUTING -d $RDS_IP -p tcp --dport 5432 -j MASQUERADE

4.2 Regras do Security Group​

Security Group do EC2:

  • Entrada: UDP 51820 de 0.0.0.0/0 (WireGuard)
  • Entrada: TCP 22 do seu IP (SSH)
  • Saída: Todo o tráfego

Security Group do RDS:

  • Entrada: TCP 5432 do Security Group do EC2
  • Entrada: TCP 5432 do IP privado do EC2

Passo 5: Executando o Benchmark​

5.1 Testar a partir do EC2 (via WireGuard)​

# Testar cada backend diretamente
for backend in "gru:10.50.1.1" "iad:10.50.2.1" "lhr:10.50.3.1"; do
region=$(echo $backend | cut -d: -f1)
ip=$(echo $backend | cut -d: -f2)
echo "=== $region ==="
curl -s http://$ip:8080/api/rds/benchmark | jq '{region, insert_avg_ms, read_avg_ms}'
done

5.2 Testar via edgeProxy (geo-routing)​

# O edge-proxy irá rotear baseado no IP do cliente
curl -s http://54.171.48.207:8080/api/rds/benchmark | jq .

5.3 Script Completo de Benchmark​

#!/bin/bash
echo "=== Benchmark RDS: Fly.io → AWS RDS Irlanda ==="
echo ""
printf "| %-8s | %-6s | %-13s | %-11s | %-9s |\n" "Bandeira" "Região" "Localização" "Insert (ms)" "Read (ms)"
echo "|----------|--------|---------------|-------------|-----------|"

for backend in \
"🇧🇷:gru:10.50.1.1:São Paulo" \
"🇺🇸:iad:10.50.2.1:Virginia" \
"🇺🇸:ord:10.50.2.2:Chicago" \
"🇺🇸:lax:10.50.2.3:Los Angeles" \
"🇬🇧:lhr:10.50.3.1:Londres" \
"🇩🇪:fra:10.50.3.2:Frankfurt" \
"🇫🇷:cdg:10.50.3.3:Paris" \
"🇯🇵:nrt:10.50.4.1:Tóquio" \
"🇸🇬:sin:10.50.4.2:Singapura" \
"🇦🇺:syd:10.50.4.3:Sydney"
do
flag=$(echo $backend | cut -d: -f1)
region=$(echo $backend | cut -d: -f2)
ip=$(echo $backend | cut -d: -f3)
location=$(echo $backend | cut -d: -f4)

result=$(curl -s --connect-timeout 10 http://$ip:8080/api/rds/benchmark 2>/dev/null)

if [ -n "$result" ]; then
insert=$(echo $result | jq -r '.insert_avg_ms' | xargs printf "%.2f")
read=$(echo $result | jq -r '.read_min_ms' | xargs printf "%.2f")
printf "| %-8s | %-6s | %-13s | %11s | %9s |\n" "$flag" "$region" "$location" "$insert" "$read"
else
printf "| %-8s | %-6s | %-13s | %11s | %9s |\n" "$flag" "$region" "$location" "TIMEOUT" "TIMEOUT"
fi
done

Referência da API​

GET /api/rds/benchmark​

Executa benchmarks de INSERT e SELECT no banco de dados RDS configurado.

Parâmetros de Query:

  • iterations (opcional): Número de iterações (1-100, padrão: 10)

Resposta:

{
"region": "lhr",
"db_host": "10.50.0.1",
"iterations": 10,
"read_avg_ms": 18.72,
"read_min_ms": 11.89,
"read_max_ms": 65.45,
"insert_avg_ms": 25.39,
"insert_min_ms": 24.60,
"insert_max_ms": 29.04,
"read_latencies": [65.45, 12.10, 11.99, ...],
"insert_latencies": [24.97, 25.62, 24.60, ...],
"timestamp": "2025-12-07T15:48:02Z"
}

GET /api/rds/health​

Retorna o status da conexão com o banco de dados.

Resposta:

{
"region": "lhr",
"db_host": "10.50.0.1",
"status": "conectado"
}

Solução de Problemas​

Problema: "no pg_hba.conf entry for host"​

Causa: O RDS exige SSL por padrão.

Solução: Desabilitar requisito de SSL:

aws rds modify-db-parameter-group \
--db-parameter-group-name edgeproxy-nossl \
--parameters "ParameterName=rds.force_ssl,ParameterValue=0,ApplyMethod=pending-reboot"

Problema: Timeout de conexão do Fly.io​

Causa: WireGuard não está conectando ao hub EC2.

Solução:

  1. Verificar se a chave pública do EC2 está correta no entrypoint.sh
  2. Verificar se o security group do EC2 permite UDP 51820
  3. Verificar regras NAT no EC2:
iptables -t nat -L -n -v

Problema: Resposta "Banco de dados não configurado"​

Causa: Secret DB_HOST não configurado.

Solução:

fly secrets set DB_HOST=10.50.0.1 -a edgeproxy-backend

Alocação de IPs WireGuard​

RegiãoIP WG Fly.ioPropósito
EC2 Hub10.50.0.1Hub WireGuard + NAT para RDS
gru10.50.1.1América do Sul
iad10.50.2.1EUA Leste
ord10.50.2.2EUA Central
lax10.50.2.3EUA Oeste
lhr10.50.3.1Europa (Reino Unido)
fra10.50.3.2Europa (Alemanha)
cdg10.50.3.3Europa (França)
nrt10.50.4.1Ásia (Japão)
sin10.50.4.2Ásia (Singapura)
syd10.50.4.3Oceania (Austrália)

Dicas de Otimização de Performance​

  1. Use connection pooling: O backend Go usa SetMaxOpenConns(10) e SetMaxIdleConns(5)

  2. Conexões persistentes: WireGuard PersistentKeepalive = 25 mantém os túneis ativos

  3. Coloque o RDS na mesma região do hub: EC2 e RDS em eu-west-1 minimiza latência interna

  4. Considere read replicas: Para workloads com muita leitura, deploy de réplicas de leitura do RDS em outras regiões