다보리 - 백엔드 살펴보기

백엔드 내부 흐름도

다보리 백엔드에서는 프론트엔드가 요청을 하게되면  

Router -> Middleware -> Controller -> Handler(Controller도 Model도 되는 애매모호한 함수들이 서로 호출하며 결과값 도출) -> Model 순으로 가며 Controller에서 데이터베이스의 값들을 Json형식으로 변환 후 프론트엔드로 보낸다. 

백엔드 main_api 설치해보기

MAIN_API 설치 방법!

    1.  WSL을 설치합니다.

    2.  아이디와 비밀번호를 지정하여 서버에 접속합니다. 
         예) sudo adduser <username>.

     3. home 경로에 새로운 백엔드를 설치할 디렉토리를 생성합니다.

      cd /home

      mkdir main_api

      4. 생성이 됐으면 main_api 폴더로 들어갑니다. (cd main_api)

      5. wget http://dbr02-wget.daboryhost.com/main_api.tar.gz 를 입력
          (wget은 웹페이지에서 파일을 다운로드하기 위한 도구)

설치가 완료되었으면 아래 디렉토리들이 존재하는지 확인해주세요. 

(1). cache-key-pair

(2). weberp-go

(3). weberp-queries 

(4). mybin 

     6. 파일들이 전부 존재하면 디렉토리 접근 후 실행파일 가동하여 서버 시작합니다

        1. cd weberp-go

        2. ./weberp-go


MAIN_API와 프론트엔드 연결하기

Docker(프론트엔드)와 로컬(백엔드)에 각각 띄웠다는 가정하에 설명을 시작하겠습니다

.env.dabory
MAIN_API_URL='http://host.docker.internal:18080' <- URL만 수정해주면 됩니다.

MAIN_API_URL = host.docker.internal:(포트)

프론트엔드 애플리케이션이 Docker 컨테이너에서 실행되고, 백엔드 서버는 로컬(호스트 머신)에서 실행될 때

MAIN_API_URL= localhost:(포트)

Docker를 사용하지 않는 순수 로컬 개발 환경이라면 이렇게 사용


ThunderClient로 통신 테스트
url = http://localhost:18080/gate-token-get 
body에 키 값 넣고 (ClientId, Base64) 게이트 토큰 받기 -> 게이트토큰 발급 후 헤더에 넣어서 엔드포인트 요청 -> 요청이 돌아오면 성공

Golang 변경사항 *

Golang 변경사항 *

개선된 Limit 사용 방식

1. 기존 방식 : Limit와 IsntPagination

기존의 백엔드 설계 방식에서는 모든 데이터를 가져오기 위해 다음과 같이 설계되었습니다

하지만 이러한 접근 방식은 다음과 같은 문제가 있었습니다

2. 개선된 방식

IsntPagination ListType1 파라미터 옵션을 제거하고, Limit = 0으로 설정하면 모든 데이터를 한 번에 가져오도록 설계되었습니다.

 (Limit > 500 이상이면 0으로 처리됩니다)

개선 내용 :


3. Query File에서의 Limit 사용

페이징 처리가 필요한 경우만 쿼리 파일에 LIMITOFFSET을 명시적으로 설정하면 됩니다. (페이징 처리가 불필요할 경우 안넣어줘도 됩니다)

SELECT
    mx.id as id,
    turbo_thumb as c1,
    item_name as c2,
    item_slug as c3
FROM dbr_item as mx
    INNER JOIN dbr_igroup as mb ON mx.igroup_id = mb.id
-- @where
limit 1

LIMIT은 반환해야 하는 데이터의 시작 위치(OFFSET)와 개수를 계산하기 때문에 추가적인 연산 작업이 발생하기 때문에 쿼리 속도가 늦어집니다.


DB에서 ListToken 활용

ListToken 필드란?

ListToken의 동작 방식

  1. 새로운 필터로 조회할 때마다 기존의 DbtListType1의 데이터가 삭제 후 재삽입됨

  2. 새로운 데이터가 삽입될 때 새로운 ListToken 값이 생성됨

  3. 이를 통해 사용자가 필터한 회원 데이터로 다양한 작업(Pick, Act, Page 등)을 수행 가능

활용 예시

동작 설명
회원 목록 필터링 특정 조건으로 필터링한 회원 목록을 DbtListType1에 저장
ListToken 활용 동일한 ListToken을 통해 특정 필터링된 회원 집합을 유지
데이터 재사용 한 번 조회된 데이터를 Pick, Act, Page 등의 작업에 활용

이 방식으로 회원 데이터 조회의 유연성을 높이며, 필터링된 결과를 효율적으로 활용할 수 있도록 설계됨

대용량 데이터 처리를 위한 기술 활용 (Kafka, ES)

대용량 데이터 처리 및 Kafka + Bulk 활용

데이터 처리 흐름

  1. DB -> Kafka -> ElasticSearch 순서로 데이터가 전달됨

  2. Kafka를 bucket 단위로 나누어 데이터 전송하여 효율적인 처리

  3. ElasticSearch에는 Bulk API를 활용하여 대량의 데이터를 일괄 삽입

주요 개선 사항

  1. Kafka를 Bucket 단위로 나눠서 전송 → 데이터 스트리밍 성능 최적화

  2. ElasticSearch에 Bulk 처리 적용 → 인덱싱 성능 향상 및 네트워크 부하 감소

  3. 대용량 데이터(150,000개 이상)를 효율적으로 처리하여 빠른 데이터 동기화 가능

Kafka Bucket 처리 예시

qryCnt := models.QryCount(y.Db, cntStr)
	fmt.Println("qryCnt:", qryCnt)

	bucketCnt := 0
	if qryCnt == 0 {
		bucketCnt = 0
	} else {
		bucketCnt = 1 + (int(qryCnt) / p.Limit)
	}

    fmt.Println("bucketCnt:", bucketCnt)
	for i := 0; i < bucketCnt; i++ {
		// for i := 0; i < 3; i++ {
		p.Offset = p.Limit * i
		sqlStr, _, _ := models_join.ListType1PlainQryStrGet(y, lt, p, q, "/list")
		if err := t.ProduceaBucketToKafka(y, "kkk", headers, sqlStr, topic); err != nil {
			return errors.New(e.PageQryErr("vrt452", ": "+err.Error()))
		}
	}

👉 Bucket을 활용하여 병렬 처리 가능!

ElasticSearch Bulk API 예시

	for _, row := range v.Page {
		row.ClientCode = clientCode
		fmt.Printf("BuyerId: %d, LastSorderDate: %s, ClientCode : %s\n", row.Id, row.LastSorderDate, row.ClientCode)
		row.LastVistDate = row.LastSorderDate
		// Meta 데이터 작성
		meta := fmt.Sprintf(`{ "index": { "_index": "%s", "_id": "%d" } }%s`, esIndex, row.Id, "\n")
		bulkRequest.WriteString(meta)

		// 문서 데이터 작성
		data, _ := json.Marshal(row)
		bulkRequest.WriteString(string(data) + "\n")
		fmt.Println(string(data)) // 데이터를 출력하여 검증

		fmt.Printf("Prepared document for BuyerId: %d\n", row.Id)
	}

	// Bulk 요청 전송
	res, err := es.Bulk(bytes.NewReader(bulkRequest.Bytes()))
	if err != nil {
		return fmt.Errorf("bulk request failed: %w", err)
	}
	defer res.Body.Close()

	// Elasticsearch 응답 확인
	if res.IsError() {
		log.Printf("Bulk indexing failed: %s", res.String())
		return fmt.Errorf("bulk indexing failed")
	}

👉 Bulk API를 활용하면 한 번에 많은 데이터를 삽입 가능!

이 방식은 대량의 데이터를 효율적으로 처리하고 성능을 최적화할 수 있도록 설계됨.

Echo에서 HTTP 응답 반환

1. 개요

Dabory의 Go 언어에서는  Echo 프레임워크로 프론트엔드 단에 응답데이터로 Json 데이터를 넘겨줘야 하기때문에                                 c.String()c.JSONBlob()을 사용하여 HTTP 응답을 Json으로 처리하는 방법을 설명한다.


2. c.String(statusCode int, response string) 사용법

✅ 개요

✅ 사용 예제

var c echo.Context
return c.String(607, "")

✅ 동작 방식


3. c.JSONBlob(statusCode int, jsonData []byte) 사용법

✅ 개요

✅ 사용 예제

ret, err := json.Marshal(vRet) if err != nil { return c.String(500, "Failed to encode JSON: "+err.Error()) } return c.JSONBlob(http.StatusOK, ret)

✅ 동작 방식

  1. json.Marshal(vRet)을 사용하여 ZbaksanSorderEyetestRes 구조체를 JSON 데이터로 변환한다.
  2. 변환된 JSON을 c.JSONBlob(http.StatusOK, ret)을 통해 프론트엔드로 전송한다.
  3. 프론트엔드는 response.json() 또는 response.data로 JSON을 받을 수 있다.

4. c.String() vs c.JSONBlob() 비교

기능 c.String() c.JSONBlob()
응답 형식 문자열(String) JSON (바이트 배열)
사용 목적 단순 메시지, 상태 코드 반환 JSON 데이터 응답
예제 코드 c.String(607, "") c.JSONBlob(http.StatusOK, ret)
프론트에서 처리 response.text() response.json() 또는 response.data

5. 요약

c.String(607, "")

c.JSONBlob(http.StatusOK, ret)