---
id: 8wel7k12347894gsdjkfnru23kl78989
author: Yunjin Lee
title: 'Go HTTP Dosya Yükleme Yanıt Gecikmesinin Azaltılması: Kanal Tabanlı Asenkron İşçi Kuyruğunun Uygulanması'
description: net/http dosya yükleme işleyicisinde, geçici dosya alımı ile Incus konteynerlerine aktarım işlemini arabellekli kanal ve goroutine çalışan havuzu (worker pool) aracılığıyla ayırarak uç nokta yanıt gecikmesini azaltan bir yapıdır.
language: tr
date: 2025-07-14T11:51:27.614782351Z
path: /blog/posts/go-handle-file-upload-with-concurrency-s844ce238
category: concurrency
---

# Genel Bakış

Go dilini ilk öğrenirken, arka uç sunucusunu uygulayarak öğrenmek sık görülen bir durumdur. Bu noktada RestAPI gibi ortamlarda dosya akışını alıp sunucuya yükleyen bir örnek oluşturmayı ele alalım.
Go dilinin net/http sunucusu varsayılan olarak birden fazla isteği eşzamanlı işlediğinden, eşzamanlı ve yoğun yükleme işlemlerinin kendisinde bir sorun yoktur.
Ancak akış alındıktan sonraki tüm işlemler eşzamanlı olarak gerçekleştirilirse, uç noktanın yanıtı gecikir.
Bu durumu önlemek için kullanılabilecek tekniği birlikte inceleyelim.

# Neden

Akışın alınması da genellikle uzun sürer ve büyük dosyalarda tek bir istek birkaç dakika boyunca işlenebilir.
Bu nedenle akışın alınmasından sonraki işlemleri mümkün olduğunca hızlı gerçekleştirmek önemlidir.
Bu örnek senaryo, akışı alıp geçici bir dosyaya kaydetmeyi ve ardından bir konteynere göndermeyi içermektedir.
Bu aşamada geçici dosyanın konteynere gönderilmesi kısmını bir çalışan havuzu (worker pool) ile işlersek, yanıt gecikmesini azaltabiliriz.

```go
package file_upload

import (
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"time"
)

const uploadTempDir = "/tmp/incus_uploads" // Ana makinedeki geçici dizin

// UploadTask, eşzamanlı olmayan dosya gönderimi için verileri tutar.
type UploadTask struct {
	HostTempFilePath         string
	ContainerName            string
    HostFilename             string
	ContainerDestinationPath string
}

// UploadHandler dosya yüklemelerini işler. Geçici dosyaya kaydeder, ardından Incus gönderimi için kuyruğa ekler.
func UploadHandler(wr http.ResponseWriter, req *http.Request) {
	if req.Method != http.MethodPost {
		http.Error(wr, "POST method required.", http.StatusMethodNotAllowed)
		return
	}
	originalFilePath := req.Header.Get("X-File-Path")
    originalFilename := filepath.Base(req.Header.Get("X-Host-Path"))
	containerName := req.Header.Get("X-Container-Name")
	if originalFilePath == "" || containerName == "" {
		http.Error(wr, "Missing X-File-Path or X-Container-Name header.", http.StatusBadRequest)
		return
	}

	cleanContainerDestPath := filepath.Clean(originalFilePath)
	if !filepath.IsAbs(cleanContainerDestPath) {
		http.Error(wr, "File path must be absolute.", http.StatusBadRequest)
		return
	}

	// Ana makinede benzersiz geçici dosya yolu oluştur
	tempFileName := fmt.Sprintf("%d-%s", time.Now().UnixNano(), filepath.Base(originalFilePath))
	hostTempFilePath := filepath.Join(uploadTempDir, tempFileName)

	if err := os.MkdirAll(uploadTempDir, 0755); err != nil {
		log.Printf("ERROR: Failed to create temp upload directory: %v", err)
		http.Error(wr, "Server error.", http.StatusInternalServerError)
		return
	}

	// İstek gövdesini geçici dosyaya oluştur ve kopyala (eşzamanlı)
	outFile, err := os.Create(hostTempFilePath)
	if err != nil {
		log.Printf("ERROR: Failed to create temporary file: %v", err)
		http.Error(wr, "Server error.", http.StatusInternalServerError)
		return
	}
	defer outFile.Close()

	bytesWritten, err := io.Copy(outFile, req.Body)
	if err != nil {
		outFile.Close()
		os.Remove(hostTempFilePath)
		log.Printf("ERROR: Failed to copy request body to temp file: %v", err)
		http.Error(wr, "File transfer failed.", http.StatusInternalServerError)
		return
	}
	log.Printf("Upload Info: Received %d bytes, saved to %s.", bytesWritten, hostTempFilePath)

	// Eşzamanlı Incus gönderimi için görevi kuyruğa ekle
	task := UploadTask{
		HostTempFilePath:         hostTempFilePath,
		ContainerName:            containerName,
        HostFilename :            originalFilename,
		ContainerDestinationPath: cleanContainerDestPath,
	}
	EnqueueTask(task) //THIS PART
	log.Printf("Upload Info: Task enqueued for %s to %s.", originalFilePath, containerName)

	// Hemen 202 Accepted yanıtı gönder
	wr.WriteHeader(http.StatusAccepted)
	fmt.Fprintf(wr, "File '%s' queued for processing on container '%s'.\n", originalFilename, containerName)
}
```

Burada THIS PART olarak işaretlediğimiz kısma baktığınızda, görevin kuyruğa eklendiğini fark etmişsinizdir.

Şimdi iş kuyruğunun nasıl çalıştığına bakalım.

```go
package file_upload

import (
	"log"
	"sync"
)

var taskQueue chan UploadTask
var once sync.Once

// InitWorkQueue bellek içi görev kuyruğunu başlatır.
func InitWorkQueue() {
	once.Do(func() {
		taskQueue = make(chan UploadTask, 100)
		log.Println("Upload Info: Work queue initialized.")
	})
}

// EnqueueTask kuyruğa bir UploadTask ekler.
func EnqueueTask(task UploadTask) {
	if taskQueue == nil {
		log.Fatal("ERROR: Task queue not initialized.")
	}
	taskQueue <- task
	log.Printf("Upload Info: Queue: Task enqueued. Size: %d", len(taskQueue))
}

// DequeueTask kuyruktan bir UploadTask alır, kuyruk boşsa bloke eder.
func DequeueTask() UploadTask {
	if taskQueue == nil {
		log.Fatal("ERROR: Task queue not initialized.")
	}
	task := <-taskQueue
	log.Printf("Upload Info: Queue: Task dequeued. Size: %d", len(taskQueue))
	return task
}

// GetQueueLength mevcut kuyruk boyutunu döndürür.
func GetQueueLength() int {
	if taskQueue == nil {
		return 0
	}
	return len(taskQueue)
}
```

Örnek olarak sunulan iş kuyruğu basit biçimde uygulanmıştır. Bu iş kuyruğu, kuyruğa eklenmiş görevleri kanaldan çıkaran basit bir yapıya sahiptir.

Aşağıda, dosya yüklemesinden sonra konteynere gönderim yapmak için kullanılan worker metodu yer almaktadır.
Mevcut metot, iyi bir yanıt verebilirlik ve kolay uygulama için sonsuz bir döngü kullanmaktadır; ancak kullanım amacına göre algoritmalar eklenebilir.

```go
func StartFilePushWorker() {
	for {
		task := DequeueTask()
		log.Printf("Upload Info: Worker processing task for %s from %s.", task.ContainerName, task.HostFilename)

		// Geçici dosyanın temizliğini ertele
		defer func(filePath string) {
			if err := os.Remove(filePath); err != nil {
				log.Printf("ERROR: Worker: Failed to remove temp file '%s': %v", filePath, err)
			} else {
				log.Printf("Upload Info: Worker: Cleaned up temp file: %s", filePath)
			}
		}(task.HostTempFilePath)

		// Geçici Incus hataları için yeniden denemelerle görevi işle
		for i := 0; i <= MaxRetries; i++ {
			err := processUploadTask(task) //separate upload task
			if err == nil {
				log.Printf("SUCCESS: Worker: Task completed for %s.", task.ContainerName)
				break
			}

			isTransient := true
			if err.Error() == "incus: container not found" { // Kalıcı hataya örnek
				isTransient = false
			}

			if isTransient && i < MaxRetries {
				log.Printf("WARNING: Worker: Task failed for %s (attempt %d/%d): %v. Retrying.",
					task.ContainerName, i+1, MaxRetries, err)
				time.Sleep(RetryDelay)
			} else {
				log.Printf("ERROR: Worker: Task permanently failed for %s after %d attempts: %v.",
					task.ContainerName, i+1, err)
				break
			}
		}
	}
}
```

Öncelikle bu fonksiyon sürekli bir döngüde dönerek kuyruktan görevleri almaya çalışır. Ardından yeniden deneme kapsamı içinde, akıştan geçici dosyaya değil, geçici dosyadan konteynere yükleme aşamasını dener.

# Avantajlar

Bu işleme yönteminin avantajı, **yalnızca akış yüklemesi başarılı olduğu sürece** sonraki işlenen görevlerin gecikme süresinin azaltılabilmesi ve eşzamanlı çok sayıda konteyner işleminden kaynaklanan kaynak tükenmesinin önlenebilmesidir. Mevcut kodda görüldüğü gibi, aynı anda ilerleyebilen konteyner işlemleri kanalın kapasitesiyle sınırlandırılmaktadır. Bu şekilde Go'nun paralel işleme özelliğini pratik olarak kullanabileceğimiz bir örneği incelemiş olduk. Daha fazla örnek görmek isterseniz, aşağıdaki bağlantıları ziyaret edebilirsiniz.
[Örnekleri içeren modül](https://github.com/gg582/linux_virt_unit)
[Örneği kullanan proje](https://github.com/gg582/incuspeed)
Proje kendisinde birçok yardımcı bileşen bulunduğundan, worker'ın kendisi hakkında öğrenmek için main.go dosyasında worker init fonksiyonunun nasıl çağrıldığına kısaca bakıp geçmeniz yeterlidir. Modül, başka türde worker'lar da içermektedir; lütfen bunlara da göz atın.
