Zum Inhalt springen

Optimieren Sie die Object-Storage-Leistung mit dem AWS Go SDK

Zuletzt aktualisiert am

Um einen hohen Durchsatz bei der Interaktion mit Object Storage zu erreichen, ist es wichtig zu verstehen, wie das AWS SDK for Go die Datenübertragung handhabt. Die Leistungsoptimierung umfasst zwei Hauptdimensionen:

  • Arbeiter (horizontale Parallelität): Dies bezieht sich auf die Anzahl der separaten Dateien, die gleichzeitig hochgeladen werden. Die Erhöhung der Worker ist die effektivste Möglichkeit, die Leistung für kleine Dateien zu verbessern, bei denen der Overhead des HTTP-Handshakes den Hauptengpass darstellt.
  • Parallelität (vertikale Parallelität): Dies wird vom S3 Transfer Manager verwaltet. Es bestimmt, wie viele Teile einer einzelnen großen Datei parallel mit Multipart Upload hochgeladen werden. Dies ist entscheidend für die Auslastung der Bandbreite mit großen Dateien.

Basierend auf unseren Tests finden Sie hier beispielhafte Ausgangspunkte für verschiedene Arbeitslasten. Diese Parameter sollten basierend auf den Ressourcen Ihrer Umgebung angepasst werden.

Es gibt keine einheitliche Zauberformel für die optimale Anzahl an Workern und Parallelität, da diese vollständig von Ihrer Umgebung abhängt. Der Schlüssel liegt im Messen, Abstimmen und Wiederholen.

  1. Beginnen Sie mit workers für die Parallelisierung auf Dateiebene.
  • Ziel: Halten Sie die CPU und das Netzwerk beschäftigt, indem Sie mehrere Dateien gleichzeitig verarbeiten. Dies ist am effektivsten für Workloads mit vielen kleinen bis mittelgroßen Dateien.
    • Ausgangspunkt: Ein guter Startwert ist das 2- bis 4-Fache der Anzahl CPU-Kerne Ihres Systems. Bei einem 8-Core-System starten Sie mit 16 bis 32 Workern.
  • Begrenzende Faktoren:
  • CPU: Zu viele Worker können zu übermäßigem Kontextwechsel führen, bei dem die CPU mehr Zeit damit verbringt, zwischen Aufgaben zu wechseln, als tatsächliche Arbeit zu erledigen.
  • Speicher: Jeder Worker und die zugehörigen Upload-Aufgaben verbrauchen Speicher.
  • Datei-Handles: Ihr Betriebssystem hat eine Begrenzung der Anzahl geöffneter Dateien.
  1. Optimieren Sie concurrency für den Durchsatz einzelner Dateien.
  • Ziel: Überlastung Ihrer Netzwerkverbindung beim Hochladen einer einzelnen großen Datei.
    • Ausgangspunkt: Für große Dateien (>100 MB) in einem schnellen Netzwerk sind Werte zwischen 8 und 32 üblich. Der SDK-Standardwert ist 10.
  • Begrenzende Faktoren:
  • Netzwerkbandbreite: Wenn Ihre Netzwerkverbindung ausgelastet ist, hilft eine weitere Erhöhung der Parallelität nicht und kann aufgrund des Overheads sogar zu einer geringfügigen Leistungseinbuße führen.
    • Speicher: Jeder parallele Teil belegt einen Puffer im Speicher (PartSize). Der gesamte Speicherverbrauch für den Upload einer Datei beträgt ungefähr Workers * Concurrency * PartSize.

Befolgen Sie diesen iterativen Prozess, um die richtige Balance zu finden:

  1. Legen Sie eine Grundlinie fest.

    Führen Sie das Skript mit niedrigen, konservativen Werten aus (z. B. WORKERS=4, CONCURRENCY=4). Das ist Ihre Basisleistung.

  2. Erhöhen Sie die Anzahl der Arbeitnehmer.

    Halten Sie CONCURRENCY konstant und erhöhen Sie WORKERS schrittweise (z. B. 4, 8, 16, 32, 64). Überwachen Sie CPU-Auslastung und gesamte Upload-Zeit. Sie erreichen einen Punkt, an dem zusätzliche Worker die Leistung nicht mehr verbessern oder sogar verschlechtern. Das ist Ihre optimale Worker-Anzahl für diesen Dateisatz.

  3. Erhöhen Sie die Parallelität.

    Beginnen Sie mit Ihrer optimalen WORKERS-Anzahl und erhöhen Sie dann CONCURRENCY (z. B. 4, 8, 16, 32). Das hilft vor allem bei großen Dateien in Ihrem Datensatz. Finden Sie erneut den Punkt mit abnehmendem Nutzen.

  4. Teilegröße anpassen.

    Bei sehr großen Dateien (mehrere GB) kann ein größeres PART_SIZE_MB (z. B. 64, 128, 256) effizienter sein, da es die Gesamtzahl der Teile und API-Aufrufe pro Upload reduziert.

Durch die methodische Abstimmung dieser drei Parameter können Sie die Leistung an die spezifischen Eigenschaften Ihrer Hardware und Arbeitslast anpassen.

Dieses Skript konfiguriert den S3-Client und den Upload-Service. Es verwendet einen Worker-Pool, um mehrere Dateien parallel aus einem lokalen Verzeichnis hochzuladen. Die Leistung können Sie direkt im Block CONFIGURATION abstimmen.

Speichern als main.go

package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"path/filepath"
"sync"
"time"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/feature/s3/manager"
"github.com/aws/aws-sdk-go-v2/service/s3"
)
func main() {
// --- CONFIGURATION ---
// 1. Set your S3 Bucket and Region
bucket := "your-s3-bucket-name"
region := "eu01"
// 2. Set Local Source and Performance Parameters
srcDir := "./test-data" // Directory containing your test files
// Performance Tuning
workers := 8 // How many files to upload at the same time
concurrency := 32 // How many chunks per file to upload at once
partSizeMB := 64 // The size of each chunk in Megabytes
// ---------------------
ctx := context.TODO()
// 3. Initialize SDK with a 15s Timeout
cfg, err := config.LoadDefaultConfig(ctx,
config.WithRegion(region),
config.WithHTTPClient(&http.Client{
Timeout: 15 * time.Second,
}),
)
if err != nil {
log.Fatalf("unable to load SDK config: %v", err)
}
client := s3.NewFromConfig(cfg)
uploader := manager.NewUploader(client, func(u *manager.Uploader) {
u.PartSize = int64(partSizeMB) * 1024 * 1024
u.Concurrency = concurrency
})
// 4. Gather all files from the source directory
files, err := os.ReadDir(srcDir)
if err != nil {
log.Fatalf("failed to read directory %q: %v", srcDir, err)
}
// 5. Worker Pool Logic
jobs := make(chan string, len(files))
var wg sync.WaitGroup
start := time.Now()
fmt.Printf("Starting benchmark: %d workers, %d concurrency per file, %dMB part size\n", workers, concurrency, partSizeMB)
for w := 1; w <= workers; w++ {
wg.Add(1)
go func(workerID int) {
defer wg.Done()
for fileName := range jobs {
fullPath := filepath.Join(srcDir, fileName)
file, err := os.Open(fullPath)
if err != nil {
fmt.Printf("[Worker %d] Error opening %s: %v\n", workerID, fileName, err)
continue
}
_, err = uploader.Upload(ctx, &s3.PutObjectInput{
Bucket: &bucket,
Key: &fileName,
Body: file,
})
file.Close()
if err != nil {
fmt.Printf("[Worker %d] Error uploading %s: %v\n", workerID, fileName, err)
}
}
}(w)
}
// Send files to the worker pool
for _, f := range files {
if !f.IsDir() {
jobs <- f.Name()
}
}
close(jobs)
wg.Wait()
fmt.Printf("\nFinished! Uploaded %d files in %v\n", len(files), time.Since(start))
}
  1. Anmeldeinformationen festlegen

    Exportieren Sie Ihren AWS-Zugriffsschlüssel und Ihren geheimen Schlüssel in Ihr Terminal. Das Skript verwendet sie automatisch zur Authentifizierung.

    Terminal-Fenster
    export AWS_ACCESS_KEY_ID="YOUR_ACCESS_KEY"
    export AWS_SECRET_ACCESS_KEY="YOUR_SECRET_KEY"
  2. Bereiten Sie Testdaten vor

    Das Skript lädt Dateien aus einem lokalen Verzeichnis hoch (Standard ./test-data). Erstellen Sie zunächst das Verzeichnis:

    Terminal-Fenster
    mkdir -p ./test-data

    Erstellen Sie anschließend einige Beispieldateien, um eine Arbeitslast zu simulieren. Dafür eignet sich der Befehl dd.

    Hier finden Sie drei Beispiele für die Erstellung von Testdateien in unterschiedlichen Größen. Jede for-Schleife erstellt 1 GB Testdateien mit unterschiedlichen Dateigrößen.

    Terminal-Fenster
    # Create 4 large 256 MB file
    for i in {1..4}; do
    dd if=/dev/zero of=./test-data/large_256MB_$i.tmp bs=1M count=256 2>/dev/null
    done
    Terminal-Fenster
    # Create 20 medium 50 MB file
    for i in {1..20}; do
    dd if=/dev/zero of=./test-data/medium_50MB_$i.tmp bs=1M count=50 2>/dev/null
    done
    Terminal-Fenster
    # Create 1024 small 1 MB file
    for i in {1..1024}; do
    dd if=/dev/zero of=./test-data/small_1MB_$i.tmp bs=1M count=1 2>/dev/null
    done
  3. Passen Sie die Parameter an

    Gehen Sie in das main.go-Skript und wählen Sie im Abschnitt „Konfiguration” den richtigen Parameter aus. Ändern Sie bei Bedarf die folgenden Zeilen:

    // Performance Tuning
    workers := 8 // How many files to upload at the same time
    concurrency := 32 // How many chunks per file to upload at once
    partSizeMB := 64 // The size of each chunk in Megabytes
  4. Führen Sie das Skript aus

    Terminal-Fenster
    go mod init main.go
    go mod tidy
    go run main.go