// 연결 설정
remoteConn,err=gremlingo.NewDriverRemoteConnection(fmt.Sprintf("wss://%s:8182/gremlin",neptuneEndpoint),func(settings*gremlingo.DriverRemoteConnectionSettings){settings.TraversalSource="g"settings.TlsConfig=&tls.Config{InsecureSkipVerify:true}})// 그래프 탐색을 위한 리모트 연결
g=gremlingo.Traversal_().WithRemote(remoteConn)
Note
AWS Neptune TLS 인증서 형식은 Go 1.18 버전 이상부터는 지원되지 않으므로 DriverRemoteConnection 에 TLS 설정을 추가해야 한다.
3. 메소드 정의하기
Vertex 삽입하기
먼저 회원 테이블을 조회하여 vertex로 생성한다. vertex의 회원아이디는 기존 데이터베이스의 회원 테이블에서 사용하는 아이디(PK) 일치 시킨다. vertex의 속성값으로는 닉네임과 생년월일을 문자열 형태로 저장한다.
먼저 이미 등록되어있는 vertex 인지 체크한다. (나중에 알았지만 ID 가 같으면 그냥 덮어쓰는 것 같다.)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
funcexistsUserVertex(vertexIdstring)(bool,error){_,err:=g.V().HasLabel(userLabel).HasId(vertexId).Next()// 특정 에러값을 구분하고 싶은데 private 값이라 임의로 하드코딩하여 비교했다... ^.^
iferr!=nil&&err.Error()!=GremlinErrNextNoResultsLeftError{returnfalse,err}iferr!=nil&&err.Error()==GremlinErrNextNoResultsLeftError{returnfalse,nil}returntrue,nil}
// addUserVertex 유저 vertex 를 생성한다.
funcaddUserVertex(userInfo*User)error{// 이미 존재하는 vertex 여부를 체크한다.
exists,err:=existsUserVertex(userInfo.userId)iferr!=nil{returnerr}ifexists{returnnil}nickname,_:=userInfo.nickname.Value()birth,_:=userInfo.birth.Value()// 그래프에 vertex 추가
_,createdErr:=g.AddV(userLabel).Property(gremlingo.T.Id,userInfo.userId).Property("nickname",nickname).Property("birth",birth).Next()ifcreatedErr!=nil{returncreatedErr}returnnil}
일단 트랜잭션까지… 고려는 무시
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 회원 데이터베이스에서 현재 유효한 유저정보 조회 (아이디, 닉네임, 생년월일)
rows,err:=db.Query(`select user_id, nickname, to_char(birth, 'YYYY-MM-DD') as birth from users u where u.deleted_at is null`)iferr!=nil{panic(err)}// row 수 만큼 vertex 생성!
forrows.Next(){u:=&User{}iferr:=rows.Scan(&u.userId,&u.nickname,&u.birth);err!=nil{panic(err)}iferr:=addUserVertex(u);err!=nil{panic(err)}}
vertex 간 edge 설정
follow 같은 경우 follower, following 처럼 양방향성을 가지고 있다. 모델을 보는 관점에 따라 다르겠지만 서로 상호관계를 갖고 있기 때문에 아래처럼 양방향성 혹은 방향성이 없다고도 말할 수 있다.
하지만 위처럼 방향성이 없는 관계를 나타낼 수 없기 때문에 아래처럼 불필요한 관계를 더 만들어 낼 수도 있지만 되도록이면 아래처럼 임의의 방향으로 관계를 설정하도록 하는것이 좋다고 한다. (그래프 탐색 때문 성능때문에 임의의 단방향으로 설정 하는 것 같은데 데이터를 방향성으로 탐색할때 어떻게 해야하는지 모르겠다… 어렵네?!
일단 나는 follow라는 레이블을 갖는 edge를 만들고 현재 follow DB 구조와 동일하게 user_id → target_id 방향으로 follow edge를 생성했다. 만약 맞팔로우 관계일경우 edge 속성에 서로 팔로우 여부를 true로 저장하게 끔 구현했다.
funcaddFollowEdge(fromId,toIdstring)error{// 1. find from vertex
fromObj,err:=getUserVertex(fromId)iferr!=nil{log.Printf("[skip] skip %s",fromId)returnnil}// 2. find to vertex
toObj,err:=getUserVertex(toId)iferr!=nil{log.Printf("[skip] skip %s",toId)returnnil}// 3. follow edge 가 존재 하는지 체크
edgeId,err:=findFollowEdge(fromId,toId,toObj)iferr!=nil&&!errors.Is(err,ErrDataNotFound){returnerr}// 3.1 edge 가 없는 경우
iferrors.Is(err,ErrDataNotFound){_,err=g.V(fromObj.Data).AddE(followEdgeLabel).From(fromObj.Data).To(toObj.Data).Property(f4fEdgeProp,false).Next()// 완료 나가리
returnnil}iferr!=nil{panic(err)}// 3.2 edge 가 있는 경우
_,err=g.E().HasLabel(followEdgeLabel).HasId(edgeId).Property(f4fEdgeProp,true).Next()iferr!=nil{returnerr}// 완료 나가리
returnnil}
**follow 테이블을 조회하여 vertex 간 edge 정보를 추가한다.**
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// follow 테이블에서 edge 관계를 생성
followRows,findFollowErr:=db.Query(`select f.user_id as from_id, f.target_id as to_id from follow f where f.deleted_at is null order by f.created_at asc`)iffindFollowErr!=nil{panic(findFollowErr)}forfollowRows.Next(){varfromId,toIdstringiferr:=followRows.Scan(&fromId,&toId);err!=nil{panic(err)}log.Printf("[addFollow] %s -> %s",fromId,toId)iferr:=addFollowEdge(fromId,toId);err!=nil{panic(err)}}
결과
테스트 때문에 한 계정에 팔로우로 몰아주었는데 비쥬얼라이징 했을때 다소 징그러운 모습이 되어버렸다…
마치며
데이터는 성공적으로 마이그레이션 했으나, 내가 원하는 결과 (사용자의 팔로우의 팔로우 중 사용자와 아직 팔로우 관계가 아닌 유저를 조회) 를 정확하게는 뽑아내지는 못하였다. gremlin 언어가 사용하기 어려운 부분도 있었지만 아직 그래프 데이터베이스에 대해 잘 이해하고 있지 못해서 그런 것 같다. 또한 팔로우 관계처럼 단방향, 양방향을 구분해야 할 경우 어떻게 설계해야할지 어려운 부분이 있었다. 실제 시스템에 적용하기 위해서는 더 많은 공부가 필요할 것 같다. 😇😇😇😇