728x90
반응형

Graph DB 핸들링

  • MATCH 로 조회한 데이터는 전부 Map으로 매핑된 데이터들
    • 따라서 쿼리한 데이터를 가져올 때 GET 메서드로 데이터를 가져와야 함
    • 아래는 처음에 짰던 코드
      • 코드가 길고 가독성도 별로라서 다른 방안 고민 중 - 아래에 수정 내용 추가
result, queryErr := dbCon.Query(GetWordAndRelatedWord, map[string]interface{}{"word": word})

if queryErr != nil {
    log.Printf("[SEARCH] Query Word Error: %v", queryErr)
    return []SearchWordResult{}, queryErr
}

var queryResult []SearchWordResult

for _, data := range result {

    name, isName := data.Get("name")

    if !isName {
        log.Printf("[SEARCH] No Name Found. Ignore. Word: %s", word)
        continue
    }

    description, isDescription := data.Get("description")

    if !isDescription {
        log.Printf("[SEARCH] No Description Found. Ignore. Word: %s", word)
        continue
    }

    createdAt, isCreatedAt := data.Get("created_at")

    if !isCreatedAt {
        log.Printf("[SEARCH] No CreatedAt Found. Ignore. Word: %s", word)
        continue
    }

    updatedAt, isUpdatedAt := data.Get("updated_at")  

    if !isUpdatedAt {
        log.Printf("[SEARCH] No UpdatedAt Found. Ignore. Word: %s", word)
        continue
    }

    category, isCategory := data.Get("category")  

    if !isCategory {

        log.Printf("[SEARCH] No Category Found. Ignore. Word: %s", word)

        continue

    }

    createdBy, isCreatedBy := data.Get("created_by")

    if !isCreatedBy {
        log.Printf("[SEARCH] No CreatedBy Found. Ignore. Word: %s", word)
        continue
    }

    var node = SearchWordResult{
        Name: name.(string),
        Category: category.(string),
        Description: description.(string),
        CreatedBy: createdBy.(string),
        CreatedAt: createdAt.(string),
        UpdatedAt: updatedAt.(string),
    }
    queryResult = append(queryResult, node)

}

데이터 핸들링 수정

  • 처음에는 위처럼 데이터를 핸들링 했지만, 읽는 노드 개수가 여러개가 됨에 따라 그 데이터 형태가 달라졌다.
    • 따라서 쿼리 방식을 바꿔야 했고, 데이터 형태 파악부터 해야 했다.
      • 데이터는 배열 안에 각각 노드 Label에 따라 노드들이 배열에 담겨있었다.
      • 따라서 쿼리 결과를 iteration 하며, 각각 label과 함께 나온 데이터를 핸들링하게 끔 수정했다.
    • 쿼리 결과를 GET해서 가져왔을 때, 그 타입이 any 였다.
      • 때문에 이를 dbtype.Node로 캐스팅하고, Node의 Properties를 뽑아와 사용했다.
        • dbType은 neo4j에서 제공하는 노드 타입
        • properties는 map[string]any 타입

responseMap := make(map[string]*SearchWordResult)

for _, data := range result {
    wNode, isNode := data.Get("word")

    if !isNode {
        log.Printf("[SEARCH] No Node Found")
        continue
    }

    targetNode := wNode.(dbtype.Node)

    rNode, isRelated := data.Get("related")

    if !isRelated {
        log.Printf("[SEARCH] No Related Node Found")
        continue    
    }

    relatedNodeList := rNode.(dbtype.Node)

    wordeNode := SearchWordItem{
        Name: targetNode.GetProperties()["name"].(string),
        Category: targetNode.GetProperties()["category"].(string),
        Description: targetNode.GetProperties()["description"].(string),
        CreatedBy: targetNode.GetProperties()["created_by"].(string),
        CreatedAt: targetNode.GetProperties()["created_at"].(string),
        UpdatedAt: targetNode.GetProperties()["updated_at"].(string),
    }

    relatedNode := SearchWordItem{
        Name: relatedNodeList.GetProperties()["name"].(string),
        Category: relatedNodeList.GetProperties()["category"].(string),
        Description: relatedNodeList.GetProperties()["description"].(string),
        CreatedBy: relatedNodeList.GetProperties()["created_by"].(string),
        CreatedAt: relatedNodeList.GetProperties()["created_at"].(string),
        UpdatedAt: relatedNodeList.GetProperties()["updated_at"].(string),
    }

    if _, exists := responseMap[wordeNode.Name]; !exists {
        responseMap[wordeNode.Name] = &SearchWordResult{
            Word: wordeNode,
            Related: []SearchWordItem{},
        }
    }

    responseMap[wordeNode.Name].Related = append(responseMap[wordeNode.Name].Related, relatedNode)

}

var responseList []SearchWordResult

for _, response := range responseMap {
    responseList = append(responseList, *response)
}

노드 및 관계 읽기

  • 아래 쿼리 방식은 관계를 통한 조회이다.
    • relations가 RELATED 혹은 PARENT 인 노드 관계들을 조회하고
    • 1..2 는 hop에 관한 내용이다
      • 1 은 직접적으로 연관된 노드들
      • 2는 중간에 노드가 하나 있는 노드들
    • 조회에 성공한 노드들 정보를 리턴한다
      • SQL과 동일하게 DISTINCT 해서 리턴
      • word의 경우 name 프로퍼티에 대한 조건 쿼리랑 동일하기 때문에 하나 혹은 동일한 이름을 가진 노드들 리턴
      • related의 경우, 특정 이름을 가진 노드와 1홉 혹은 2홉의 관계를 가진 모든 노드들
MATCH (w:Word {name: $word})-[:RELATED|PARENT*1..2]-(related:Word)
RETURN DISTINCT word, related

관계 property 설정

  • 실제 짜서 사용중인 코드이다. 아래처럼 관계를 설정한다.
    • 방향이 설정되지 않은 관계는 RELATED
    • 부모/자식 노드들의 관계를 PARENT / CHILDREN이라는 관계로 정의함
// Create Relations
var CreateWordRelatinon = `
    MATCH (w1:Word{name: $name1}), (w2:Word{name: $name2})
    MERGE (w1)-[r:RELATED]-(w2)
    SET r.weight = $weight
`

// Create Relations; w1 is Parent, w2 is Children
var CreateWordParent = `
    MATCH (w1:Word{name: $name1}), (w2:Word{name: $name2})
    MERGE (w1)-[r:PARENT]->(w2)
    SET r.weight = $weight
`

// Create Relations; w2 is Parent, w1 is Children
var CreateWordChildren = `
    MATCH (w1:Word{name: $name1}), (w2:Word{name: $name2})
    MERGE (w1)-[r:CHILDREN]->(w2)
    SET r.weight = $weight
`
728x90
반응형

'백엔드 Backend > Golang' 카테고리의 다른 글

[Neo4j] Golang 으로 쿼리 핸들링  (0) 2024.12.06
[Neo4j] Golang으로 Neo4j 연동  (0) 2024.12.05
[GO] Ubuntu 서버에 설치  (1) 2024.10.30
[GO] Goroutine이란?  (0) 2024.10.02
728x90
반응형

계속 적을 글

  • 본 글은 계속해서 적어갈 예정

Create

  • 메서드 설명
    • CREATE: 동일한 내용의 노드가 있더라도 생성함
    • MERGE: 동일한 노드가 있다면 무시하고 진행함
  • 쿼리 문에 들어갈 파라미터들은 Prepared 하게 런타임에서 주입
    • Golang에서는 map 에 담아서 사용
CREATE 
(p:Person {name: $name, age: $age, languages: $languages})-[l:LIKES]->(t:Technology {name: $tech})

MERGE 
(p:Person {name: $name, age: $age, languages: $languages})-[l:LIKES]->(t:Technology {name: $tech})

// 각각 노드 생성하고 관계 추가
MERGE (a:Person {name: $name})
MERGE (b:Person {name: $friend})
MERGE (a)-[friendship:KNOWS {since: $friendsSince}]->(b)

// Golang
queryArguments := map[string]interface{}{
    "name": name,
    "age": age,
    "languages": languageList,
    "tech": techName,
}

queryErr := dbCon.Insert(CreatePersonQuery, queryArguments)

Select(Match)

  • ExecuteQuery 로 쿼리 수행하고, 나온 데이터를 핸들링한다.
    • []*db.Record 타입으로 리턴됨
내가 핸들링한 방법
  • dbtype.node 의 Props로 감싸고, 키 값으로 처리
  • 공식문서는 AsMap()으로 처리
// ============== 내가 한 방식 ==============
for _, record := range result {
    person, isPersonExist := record.Get("p")

    if !isPersonExist {
        log.Printf("[SINGLE_PERSON] No Person Data Found: %v", isPersonExist)
        continue
    }

    log.Printf("[SINGLE_PERSON] Get single person: %v", person)

    personProperties := person.(dbtype.Node).Props

    personData := Person{
        Name: personProperties["name"].(string),
        Age: personProperties["age"].(string),
        Languages: personProperties["languages"].([]string),
    }

    personList = append(personList, personData)
}

// ============== 공식문서 ==============
 if employeesN := org.AsMap()["employeesN"].(int64);
       employeesN < employeeThreshold {
        err = addPersonToOrganization(ctx, tx, name, orgId)

        if err != nil {
            return nil, err
            // Transaction will roll back
            // -> not even Person is created!
        }
    }
728x90
반응형

'백엔드 Backend > Golang' 카테고리의 다른 글

[Neo4J] 데이터 핸들링하기  (1) 2024.12.09
[Neo4j] Golang으로 Neo4j 연동  (0) 2024.12.05
[GO] Ubuntu 서버에 설치  (1) 2024.10.30
[GO] Goroutine이란?  (0) 2024.10.02
728x90
반응형

공식 문서

Golang으로 연결

  • neo4j 드라이버 설치
    • 연결의 인증은 여러 방식이 있음; BasicAuth, BearerAuth, AuthToken, CustomAuth, ...
    • 본 예시는 유저 이름과 패스워드로 인증하는 방식
go get -u github.com/neo4j/neo4j-go-driver/v5/neo4j
  • 드라이버 인스턴스 생성
import (
    "github.com/neo4j/neo4j-go-driver/v5/neo4j"
    "github.com/neo4j/neo4j-go-driver/v5/neo4j/db"
)

func main() {
    driver, err := neo4j.NewDriverWithContext(
        "neoj4://<DATABASE_URI>:<DATABASE_PORT>,
        neo4j.BasicAuth(<USER>, <PASSWD>, ""),
    )
}
  • 연결 체크
import (
    "context"

    "github.com/neo4j/neo4j-go-driver/v5/neo4j"
    "github.com/neo4j/neo4j-go-driver/v5/neo4j/db"
)

func main() {
    driver, err := neo4j.NewDriverWithContext(
        "neoj4://<DATABASE_URI>:<DATABASE_PORT>,
        neo4j.BasicAuth(<USER>, <PASSWD>, ""),
    )

    ctx := context.Background()

    err := driver.VerifyConnectivity(ctx)

    if err != nil {
        log.Printf("[GRAPH_DB] Check Connection Error: %v", err)
        return err
    }

    defer driver.Close(ctx)

    return nil
}

Golang으로 쿼리

  • 쿼리 실행
    • ExecuteQuery 메서드로 쿼리 실행
import (
    "context"

    "github.com/neo4j/neo4j-go-driver/v5/neo4j"
    "github.com/neo4j/neo4j-go-driver/v5/neo4j/db"
)

func main() {
    driver, err := neo4j.NewDriverWithContext(
        "neoj4://<DATABASE_URI>:<DATABASE_PORT>,
        neo4j.BasicAuth("<USER>", "<PASSWD>", ""),
    )

    ctx := context.Background()

    // 쿼리 실행
    result, queryErr := neo4j.ExecuteQuery(
        ctx, 
        driver, 
        "MERGE (n:Node {name:"$nodeName"})", // Node라는 유니크 노드 생성
        []map[string]interface{ // 파라미터 생성. nodeName에 들어갈 값
            nodeName: "New Node"
        }, 
        neo4j.EagerResultTransformer, // 쿼리 결과 변환
        neo4j.ExecuteQueryWithDatabase("neo4j"),
    )

    if queryErr != nil {
        return nil, queryErr
    }

    defer db.Close(ctx)

    for _, record := range result.Records {
        log.Printf("[GRAPH_DB] Query Result: %v", record.Values...)
    }

    // 쿼리 결과에 대한 요약
    log.Printf("The query `%v` returned %v records in %+v.\n",
        result.Summary.Query().Text(), len(result.Records),
        result.Summary.ResultAvailableAfter())
}
728x90
반응형

'백엔드 Backend > Golang' 카테고리의 다른 글

[Neo4J] 데이터 핸들링하기  (1) 2024.12.09
[Neo4j] Golang 으로 쿼리 핸들링  (0) 2024.12.06
[GO] Ubuntu 서버에 설치  (1) 2024.10.30
[GO] Goroutine이란?  (0) 2024.10.02
728x90
반응형

UBUNTU에 Go 설치하기

  • 항상 컨테이너로 GO 프로그램을 돌렸지만, 불가피하게 로컬에 GO를 설치해 빌드하고 돌려야 할 상황이 와서 해당 내용을 기록한다.

소스 다운 받아서 설치하기

최신 버전 확인

  • 공식 사이트의 Golang 버전 확인 후 다운받기 (2024.10.21 시점 1.23.2)
  • 공식 사이트

WGET을 이용해 다운로드

  • WGET을 이용해 다운받기
    wget https://go.dev/dl/go1.23.2.linux-amd64.tar.gz

압축 해제

  • 다운 받은 압축 파일 해제
    sudo tar -C /usr/local -xzf go1.23.2.linux-amd64.tar.gz

경로 설정

vi ~/.profile

# 파일 마지막 줄 아래에 내용 추가
export PATH=$PATH:/usr/local/go/bin
export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin

변경 사항 적용

source ~/.profile

Go 버전 확인

go version

UBUNTU 피키지 매니저로 다운받기

패키지 인덱스 업데이트

sudo apt update

GO 설치

sudo apt-get install golang-go

Go 버전 확인

go version
728x90
반응형

'백엔드 Backend > Golang' 카테고리의 다른 글

[Neo4J] 데이터 핸들링하기  (1) 2024.12.09
[Neo4j] Golang 으로 쿼리 핸들링  (0) 2024.12.06
[Neo4j] Golang으로 Neo4j 연동  (0) 2024.12.05
[GO] Goroutine이란?  (0) 2024.10.02
728x90
반응형

Golang은 뛰어난 동시성 지원이 장점이라고들 한다.

일반적으로 쓰레드를 활용하여 동시성 프로그래밍을 하지만, Golang은 고루틴 goroutine으로 가능하다.

 

고루틴 Goroutine

공시적인 설명으로는 경량 쓰레드이다. 실제 OS의 쓰레드를 사용하는 것이 아닌, golang의 런타임에서 관리되는 논리적 / 가상적 쓰레드이다.

 

사용법

go func() {

    // 대충 코드

  }()

이게 끝이다.

 

thread VS goroutine

1. 메모리 사용량

  • goroutine: 생성시 약 2KB의 스택 메모리 혹은 힙 메모리 공간만을 사용한다.
  • thread: 약 1MB 의 메모리 공간을 사용한다.
  • P.S. Guard Page: 쓰레드가 사용할 메모리 공간과 각 메모리 간의 경계 역할
  • 따라서 스레드 기반의 동시성 처리는 결국 메모리 부족 현상(OutOfMemory) 문제 발생할 수 있으므로, 스레드를 미리 생성해 재활용하는 형태가 될 수 있음.

 

2. 비용

  • goroutine: 런타임에서 논리적으로(하드웨어와 상관 없이) 생성되고 소거되기 때문에 가볍다.
  • thread: OS 에 스레드 생성을 요청해 사용하고 작업 완료 시 다시 OS에 반환해야 한다.
  • 따라서 쓰레드 생성시마다 OS에 요청해 생성하고 다시 반환하는 방식이 더 리소스 사용량이 많다.

 

3. Context Switching 비용

  • 하나의 스레드가 특정 작업 처리를 위해 사용(Blocking)된다면, 다른 쓰레드가 대신하여 처리하도록 스케쥴링 됨
  • goroutine: 3개의 레지스터(PC(Program Counter)), SP(Stack Pointer), DX)만 save/restore 작업을 함
  • thread: 스레드가 스케쥴링고 교체되는 동안 스케쥴러에서는 모든 레지스터들을 save/restore 해야함
  • 일반적으로 16개의 범용 레지스터, PC, SP, Segment 레지스터, 16개의 XMM레지스터, FP coprocessor state, 16개의 AVX 레지스터, 모든 MSR들 등 save/restore 작업을 진해해야함
  • 따라서 Context Switching 할 때 save/restore 해야하는 레지스터의 개수부터가 큰 차이가 남

 

결론

golang이 메모리 사용량과 쓰레드 생성과 작업처리에 있어서의 비용 측면에서 효율적이다.

 

마지막

goroutine의 동작 방식과 쓰레드 종류에 대해선 다음에 서술하고자 한다.

 

REFERENCE

- https://velog.io/@khsb2012/go-goroutine

 

 

728x90
반응형

'백엔드 Backend > Golang' 카테고리의 다른 글

[Neo4J] 데이터 핸들링하기  (1) 2024.12.09
[Neo4j] Golang 으로 쿼리 핸들링  (0) 2024.12.06
[Neo4j] Golang으로 Neo4j 연동  (0) 2024.12.05
[GO] Ubuntu 서버에 설치  (1) 2024.10.30

+ Recent posts