이전 글을 요약하면, 파이썬 asyncio로 tcp 서버를 만들었다.


클라의 요청을 받아서 외부 HTTP 요청(10ms 레이턴시)을 보내고, 응답을 돌려주는 형태의 서버이다.


Standard D4s v3(4개 vcpu, 16GiB 메모리)를 사용했는데, 4코어 100% 사용률임에도 불구하고 처리량이 초당 1,200건 정도 나왔다.


이게 너무 적은거 같은데, 비교군이 없어서 모르겟다.


그래서, 비교군을 만들려고 go로 서버 만들어봄. 사실 go 잘 모르고, 대충 아는 정도라 gpt로 만들었는데 결과가 의외로 비슷함...


서버는 내장 net 모듈 써서 만들었음. 비슷하게 CPU 사용률 4코어 모두 100% 찍었고 메모리 사용률은 매우 낮았음...


24b0d121e09c28a8699fe8b115ef046c60f2294899


아 참고로, 외부 API 호출없이 그냥 패킷 받은거 돌려주는 에코 서버는 GO가 나았다.


현재 추측한건 이렇슴.


외부 API를 호출하고, 응답을 받으려면 대기 시간이 필요하다.


그 대기 시간동안 CPU가 놀면, 일을 안한거라 문제인게 분명하다.


그런데, 코어 4개가 100% 찍는거면, 응답을 받기 위한 대기시간동안 다른 일을 한다는 거라 괜찮아 보인다.



GO 코드

package main

import (
    "context"
    "encoding/binary"
    "encoding/json"
    "io"
    "log"
    "net"
    "sync/atomic"
    "time"
    "github.com/Azure/azure-sdk-for-go/sdk/data/aztables"
    "github.com/google/uuid"
)

var (
    msgCounter  uint64
    tableClient *aztables.Client
)

// sendPacket: 2바이트 길이 + 2바이트 msgType + payload
func sendPacket(conn net.Conn, msgType uint16, payload []byte) error {
    header := make([]byte, 4)
    binary.BigEndian.PutUint16(header[0:2], uint16(len(payload)))
    binary.BigEndian.PutUint16(header[2:4], msgType)
    if _, err := conn.Write(header); err != nil {
        return err
    }
    if _, err := conn.Write(payload); err != nil {
        return err
    }
    return nil
}

// readPacket: header를 읽고 payload를 io.ReadFull로 정확히 채움
func readPacket(conn net.Conn) (msgType uint16, payload []byte, err error) {
    header := make([]byte, 4)
    if _, err = io.ReadFull(conn, header); err != nil {
        return 0, nil, err
    }
    length := binary.BigEndian.Uint16(header[0:2])
    msgType = binary.BigEndian.Uint16(header[2:4])

    payload = make([]byte, length)
    if _, err = io.ReadFull(conn, payload); err != nil {
        return 0, nil, err
    }
    return msgType, payload, nil
}

func handleConnection(conn net.Conn) {
    defer conn.Close()
    for {
        msgType, payload, err := readPacket(conn)
        if err != nil {
            if err != io.EOF {
                log.Printf("readPacket error: %v", err)
            }
            return
        }
        atomic.AddUint64(&msgCounter, 1)

        // --- JSON 마샬링 후 비동기 업서트 ---
        partitionKey := uuid.NewString()
        rowKey       := uuid.NewString()
        entity := aztables.EDMEntity{
            Entity: aztables.Entity{
                PartitionKey: partitionKey,
                RowKey:       rowKey,
            },
            Properties: map[string]any{
                "name": "홍길동",
                "abc":  "예시값",
            },
        }

        // Marshal는 메인 고루틴에서 실행
        data, err := json.Marshal(entity)
        if err != nil {
            log.Printf("marshal entity error: %v", err)
        } else {
            // 비동기 Fire-and-forget 업서트
            go func(b []byte) {
                if _, err := tableClient.UpsertEntity(context.Background(), b, nil); err != nil {
                    log.Printf("async upsert error: %v", err)
                }
            }(data)
        }
        // ------------------------------------

        // 즉시 echo 응답
        if err := sendPacket(conn, msgType, payload); err != nil {
            log.Printf("sendPacket error: %v", err)
            return
        }
    }
}

func main() {
    // 1) Azure Table Storage 클라이언트 초기화
    connStr := ""
    serviceClient, err := aztables.NewServiceClientFromConnectionString(connStr, nil)  // 연결 문자열 사용 :contentReference[oaicite:2]{index=2}
    if err != nil {
        log.Fatalf("failed to create service client: %v", err)
    }
    tableName := "AbcTable"
    tableClient = serviceClient.NewClient(tableName)  // 테이블 클라이언트 생성

    // 2) TCP 서버 시작
    ln, err := net.Listen("tcp", ":31000")
    if err != nil {
        log.Fatalf("listen error: %v", err)
    }
    defer ln.Close()
    log.Println("Server listening on :31000")

    // 초당 메시지 처리량 로깅
    go func() {
        ticker := time.NewTicker(time.Second)
        defer ticker.Stop()
        for range ticker.C {
            count := atomic.SwapUint64(&msgCounter, 0)
            log.Printf("Throughput: %d messages/sec", count)
        }
    }()

    for {
        conn, err := ln.Accept()
        if err != nil {
            log.Printf("accept error: %v", err)
            continue
        }
        go handleConnection(conn)
    }
}