Closed sypark9646 closed 3 years ago
음 저도 레디스 설계 해보고 코멘트 달겠습니다
찾아보니 redis가 파이어베이스처럼 url 형식의 collection을 가지고 있지는 않네요.. 댓글 공감 기능순으로 정렬한다면 sorted set을 사용하면 편할 것 같은데 좀 더 찾아보겠습니다
아하 지금 저장 방식은 그대로 사용하고 게시글 postID = postID1 에 해당하는 commentID 모음을 sorted set으로 만들어 두자는 말씀이시죠??
"postID1": { "commentID1", "commentID2", "commentID3"} // sorted set
"commentID1": {
"id": commentID1,
"postId": postID1,
"content": "content",
...
}
"commentID2": {
"id": commentID2,
"postId": postID1,
"content": "content",
...
}
"commentID3": {
"id": commentID3,
"postId": postID1,
"content": "content",
...
}
생각해보니.. redis 에 key pattern으로 get 하는 기능이 있었네요
수정 기능 구현 시에는 set "postID1:commentID1" ~~
이런식으로 하고
게시물에 해당하는 댓글들을 가져올땐 get postID1:[A-Za-z0-9]*
(이 정규표현식이 맞는지는 모르겠지만..ㅎㅎ) 이런식으로 하면 될 것 같아요!
"postID1:commentID1": {
"id": commentID1,
"postId": postID1,
"content": "content",
"password": "password1",
}
"postID1:commentID2": {
"id": commentID2,
"postId": postID1,
"content": "content",
"password": "password2",
}
"postID1:commentID3": {
"id": commentID3,
"postId": postID1,
"content": "content",
"password": "password3",
}
"commentID1": 10
"commentID2": 15
"commentID3": 23
그리고 공감 처리는 따로 INCR commentID1
이런식으로 할 수 있도록 따로 뺄려고 하는데 어떠신가요?
(그런데 이렇게 되면 댓글 정보를 가져올때 따로 처리를 해줘야 하는게 있어서 귀찮으려나..)
앗 그리구 commentID 는 qc-server에서 만들어서 주는 것 맞나용??
앗 그리구 commentID 는 qc-server에서 만들어서 주는 것 맞나용??
엇 아뇨 snoflake가 go이길래 go에서 하는 줄 알았습니다ㅜㅠ 저희쪽에서 만들어서 드릴까요?
엇 아뇨 snoflake가 go이길래 go에서 하는 줄 알았습니다ㅜㅠ 저희쪽에서 만들어서 드릴까요?
아니용!!ㅋㅋㅋㅋㅋ go 에서 해도 됩니다!! 혹시나 여쭤봤어요
엇 아뇨 snoflake가 go이길래 go에서 하는 줄 알았습니다ㅜㅠ 저희쪽에서 만들어서 드릴까요?
아니용!!ㅋㅋㅋㅋㅋ go 에서 해도 됩니다!! 혹시나 여쭤봤어요
네넵
지금 저장 방식은 그대로 사용하고 게시글 postID = postID1 에 해당하는 commentID 모음을 sorted set으로 만들어 두자는 말씀이시죠??
말씀해주신 지금의 방식이라는 게 hash를 사용하는 방식인가요? 일단은 저걸 말한 건 맞습니다. 처음에는 저는 post id를 key로 두고 value는 comment id만 저장해서 sorted set으로 저장하면서 동시에 hash에 comment 내용들을 넣는 방식을 생각했습니다.
postId가 키여서 postId당 댓글 id를 가져오는 건 편해도, 어차피 hash에서 commentId 일일이 가져올 바에야 말씀해주신 key pattern 방식이 차라리 나을 것 같기도 합니다
그리고 comment id 생성 시에 BOF가 날거라 생각했는데, 찾아보니 golang의 uint64 값이 꽤 커서 걱정안해도 될 수준이긴 하네요.. uuid생성하셔도 되고 uint64로 해도 될것 같습니다!
그래서 필드 패턴은 post:${post-id}:comment:${comment-id}
가 괜찮다고 생각했습니ㅣ다..만
패턴 매칭이 시간이 얼마나 걸릴지 모르겠네요.. KEYS
커맨드는 모두 스캔해야하기 때문에 거의 금기되어있는 것 같고, 키 패턴 매칭에 쓰이는 HSCAN
도 페이지 단위로 읽어서 일정 개 이상의 comment를 가져오지 않으면 다음 커서를 읽어야 할 수도 있을 것 같고..
무엇보다 hash 에 json 전체를 저장한다면 공감이나 시간 순 정렬을 어떻게 할 수 있을지 모르겠네요
# post 1에 comment 3개 넣기 (대충 comment id 1,2,3으로 설정)
(redis-cli)> hmset quicoment post:1:comment:1 "{\"timestamp\": \"2021-08-00 00:00:00.000000\", \"postId\": 1, \"id\": 1, \"content\": \"content\", \"password\": \"password1\" }" post:1:comment:2 "{\"timestamp\": \"2021-08-01 00:00:00.000000\", \"postId\": 1, \"id\": 2, \"content\": \"content\", \"password\": \"password2\" }" post:1:comment:3 "{\"timestamp\": \"2021-08-05 00:00:00.000000\", \"postId\": 1, \"id\": 3, \"content\": \"content\", \"password\": \"password3\" }"
# post 2에 comment 2개 넣기 (대충 comment id 1,2,3으로 설정)
(redis-cli)> hmset quicoment post:2:comment:1 "{\"timestamp\": \"2021-08-01 00:00:00.000000\", \"postId\": 2, \"id\": 1, \"content\": \"content\", \"password\": \"password1\" }" post:2:comment:2 "{\"timestamp\": \"2021-08-05 00:00:00.000000\", \"postId\": 2, \"id\": 2, \"content\": \"content\", \"password\": \"password2\" }"
# post 1의 comment 모두 불러오기
(redis-cli)> hscan quicoment 0 match post:1:comment*
# output
1) "0"
2) 1) "post:1:comment:3"
2) "{\"timestamp\": \"2021-08-05 00:00:00.000000\", \"postId\": 1, \"id\": 3, \"content\": \"content\", \"password\": \"password3\" }"
3) "post:1:comment:1"
4) "{\"timestamp\": \"2021-08-01 00:00:00.000000\", \"postId\": 1, \"id\": 1, \"content\": \"content\", \"password\": \"password1\" }"
5) "post:1:comment:2"
6) "{\"timestamp\": \"2021-08-00 00:00:00.000000\", \"postId\": 1, \"id\": 2, \"content\": \"content\", \"password\": \"password2\" }"
이렇게 키패턴 검색이 가능하긴 한데.. 따로 sorted set을 만들지 않는 이상 json을 통째로 저장하면 hash에서의 sort로 정렬이 가능할지는 잘 모르겠네요
음 그래서 계속 생각해봤는데 모든 댓글을 하나의 해시(quicoment
라는 키)에 저장하기 때문에 패턴검색을 쓰기도 하고, 어차피 게시물이 삭제된다면 관련 댓글도 지워야 하는데, del key
를 하면 모든 value를 지울 수 있으니까 하나의 키에 하나의 post를 대응하는 게 더 좋을 것 같기도 합니다.
고민끝에 sorted set(zset)을 두 개 이용하는건 어떨까요?? post:1:like
에는 score를 공감순으로 놓고, score는 float 타입이어야 해서post1:timestamp
는 timestamp를 localdatetime이 아닌 long타입의 값으로 바꿔(제가 전달하겠습니다) score로 저장하면..
post:1:like
# zset에 post 1 comment 4개 저장
(redis-cli) > zadd post:1:like 2 "{\"timestamp\": \"2021-08-05 00:00:00.000000\", \"id\": 1, \"content\": \"content1\", \"password\": \"password1\"}"
(redis-cli) > zadd post:1:like 0 "{\"timestamp\": \"2021-08-05 00:00:01.000000\", \"id\": 2, \"content\": \"content2\", \"password\": \"password2\"}"
(redis-cli) > zadd post:1:like 0 "{\"timestamp\": \"2021-08-05 00:00:04.000000\", \"id\": 3, \"content\": \"content3\", \"password\": \"password3\"}"
(redis-cli) > zadd post:1:like 3 "{\"timestamp\": \"2021-08-05 00:01:04.000000\", \"id\": 4, \"content\": \"content4\", \"password\": \"password4\"}"
# 공감 순 정렬
(redis-cli) > zrevrange post:1:like 0 -1 with score
# output
1) "{\"timestamp\": \"2021-08-05 00:01:04.000000\", \"id\": 4, \"content\": \"content4\", \"password\": \"password4\"}"
2) "3"
3) "{\"timestamp\": \"2021-08-05 00:00:00.000000\", \"id\": 1, \"content\": \"content1\", \"password\": \"password1\"}"
4) "2"
5) "{\"timestamp\": \"2021-08-05 00:00:04.000000\", \"id\": 3, \"content\": \"content3\", \"password\": \"password3\"}"
6) "0"
7) "{\"timestamp\": \"2021-08-05 00:00:01.000000\", \"id\": 2, \"content\": \"content2\", \"password\": \"password2\"}"
8) "0"
# 공감 순 정렬, 3개까지만
(redis-cli) > zrevrange post:1:like 0 2
# output
1) "{\"timestamp\": \"2021-08-05 00:01:04.000000\", \"id\": 4, \"content\": \"content4\", \"password\": \"password4\"}"
2) "{\"timestamp\": \"2021-08-05 00:00:00.000000\", \"id\": 1, \"content\": \"content1\", \"password\": \"password1\"}"
3) "{\"timestamp\": \"2021-08-05 00:00:04.000000\", \"id\": 3, \"content\": \"content3\", \"password\": \"password3\"}"
post:1:timestamp
# zset에 post 1 comment 3개 저장
(redis-cli) > zadd post:1:timestamp 1628089200 "{\"timestamp\": \"2021-08-05 00:00:00.000000\", \"id\": 1, \"content\": \"content1\", \"password\": \"password1\"}"
(redis-cli) > zadd post:1:timestamp 1628089201 "{\"timestamp\": \"2021-08-05 00:00:01.000000\", \"id\": 2, \"content\": \"content2\", \"password\": \"password2\"}"
(redis-cli) > zadd post:1:timestamp 1628089204 "{\"timestamp\": \"2021-08-05 00:00:04.000000\", \"id\": 3, \"content\": \"content3\", \"password\": \"password3\"}"
# 시간 순 정렬
(redis-cli) > zrevrange post:1:timestamp 0 -1
# output
1) "{\"timestamp\": \"2021-08-05 00:00:04.000000\", \"id\": 3, \"content\": \"content3\", \"password\": \"password3\"}"
2) "{\"timestamp\": \"2021-08-05 00:00:01.000000\", \"id\": 2, \"content\": \"content2\", \"password\": \"password2\"}"
3) "{\"timestamp\": \"2021-08-05 00:00:00.000000\", \"id\": 1, \"content\": \"content1\", \"password\": \"password1\"}"
음 근데 그러면 시간 정렬 때 공감 수가 안보이긴 하네요..
고민이네요.. get 하기 쉬워지려면 update할게 많아지고, update 하기 쉽게 설계하려면 get 할때 따로 처리가 필요하고ㅠㅠ
패턴 매칭이 시간이 얼마나 걸릴지 모르겠네요.. KEYS 커맨드는 모두 스캔해야하기 때문에 거의 금기되어있는 것 같고, 키 패턴 매칭에 쓰이는 HSCAN도 페이지 단위로 읽어서 일정 개 이상의 comment를 가져오지 않으면 다음 커서를 읽어야 할 수도 있을 것 같고..
앗 맞아요 커서를 두고 쓰더라구요..ㅠㅠ
무엇보다 hash 에 json 전체를 저장한다면 공감이나 시간 순 정렬을 어떻게 할 수 있을지 모르겠네요
제가 생각했던 방식은
시간순 정렬: ID순으로 정렬하면 된다고 생각했었고, (인스타그램 id를 만들때 timestamp가 들어가서 id로 정렬할 수 있다는 글을 본 기억이 나서... snowflake도 동일방식인지는 아직 안찾아봤어요,,!! 아래 자료를 보니까 id 로 정렬하면 그게 시간순 정렬을 뜻하는건가 싶기도 하네요) 출처 - https://www.slideshare.net/jacking/twitter-snowflake
공감순 정렬: commentID만 set으로 저장하되, score(공감수)을 주어서 ZREVRANGE로 가져오면 어떨까 했었어요
음 근데 그러면 시간 정렬 때 공감 수가 안보이긴 하네요..
저는 일단 공감 수는 자주 바뀔 수 있는 값이라 따로 빼는게 좋지 않을까 싶은 생각이 들어요. 정렬한 commentID에 해당하는 공감수는 따로 가져와서 조합하는게 낫지 않을까 하는 생각이 드는데 어떻게 생각하시나요?,?
앗 nosql 이 읽기작업보다(join) 쓰기작업이 효율적이어서 commentID 만 저장하는 방법보다는 객체 그 자체를 저장하는 방법을 생각하신것인가요? (그런데 업데이트 하는 경우 단순 쓰기작업이 아니라 읽기+쓰기 의 조합이긴 하네요)
정렬한 commentID에 해당하는 공감수는 따로 가져와서 조합하는게 낫지 않을까 하는 생각이 드는데 어떻게 생각하시나요?,?
=> 이 방식은 안좋을수도 있겠네요,,,
sorted set(zset)을 두 개 이용하는건 어떨까요??
제일 최선인 방법인 것 같습니다..!! timestamp 는 안주셔도 될 것 같아요 snowflake id로 대체할 수 있어보입니당 업데이트할 때 트랜잭션 처리를 잘 해야겠군용...
음 근데 갑자기 생각난건데, 저번에 댓글 공감 기능을 한 명만 누를 수 있게 하기 위해 comment:{commentId}:like
set에 회원id를 넣는 방식을 논의했던 것 같은데, 제가 헷갈리는 건가 싶네요
앗 nosql 이 읽기작업보다(join) 쓰기작업이 효율적이어서 commentID 만 저장하는 방법보다는 객체 그 자체를 저장하는 방법을 생각하신것인가요?
ㅜㅜ그건 아니라 객체를 저장하지 않고 comment-id만 저장한 후에 comment 내용을 몽땅 가져올 수 있으면 좋을 것 같은데요.. 딱히 다른 방식이 생각나지 않아서요..
따로 레디스에 저장했다가 ttl을 설정해서 일정 시간이 지나면 db같은 곳에 저장하는 방식은 좀 복잡할까요?? 계속 늘어나는 comment양을 redis가 감당할 수 있을지 모르겠네요 😢
음 근데 갑자기 생각난건데, 저번에 댓글 공감 기능을 한 명만 누를 수 있게 하기 위해 comment:{commentId}:like set에 회원id를 넣는 방식을 논의했던 것 같은데, 제가 헷갈리는 건가 싶네요
네 그렇게 할까 했었는데 그러면 세션이나 로그인 기능을 구현해야할 것 같아서요..!! 저는 이 방식도 괜찮습니당
따로 레디스에 저장했다가 ttl을 설정해서 일정 시간이 지나면 db같은 곳에 저장하는 방식은 좀 복잡할까요??
좋아요!! 저도 어제 찾아보니까 ttl 설정하고 다른 DB에 스냅샷 넘기는 방식이 좋을 것 같았어용 아직 sync쪽은 서치하지 못했지만,,, 그런데 궁금한 게 있는데 만약 레디스에 있는 정보를 mysql로 백업하면 댓글 리스트를 가져올땐 mysql을 사용하고 자주 바뀌는 값(공감)은 레디스에서 가져오는 것인가요?.?
그런데 궁금한 게 있는데 만약 레디스에 있는 정보를 mysql로 백업하면
댓글 리스트를 가져올땐 mysql을 사용하고 자주 바뀌는 값(공감)은 레디스에서 가져오는 것인가요?.?
댓글 페이지네이션 할 때 레디스를 사용하는 식이 되지 않을까요? 자주바뀌는 공감 score도 레디스에서 가져오면서 뷰에 20개씩 보여준다면 스코어 높은 순으로 스코어 값과 20개의 comment id들만 레디스에서 가져오고, 그 20개 코멘트의 static한 내용은 db에서 가져온다고 이해했습니다 저는..!
앗 그렇군요..!!
그럼 정리하자면 말씀하신대로 sorted set(zset)을 아래 두 가지를 사용하도록 하고, 일정 시간마다 mysql에 백업을 한다.
음..
sorted set key를 post:{postID}:comment:{commentID}
이걸로 쓰면 post별로 가져올 때는 패턴 매칭으로 하나요?
그리고 post:{postId}:like
를 사용하면 post 별 댓글을 가져올 때 공감 순 정렬을 못하지 않나요??
저는 sorted set 한개는 post:{postID}:comment:score
로 commentid만 value로 저장하고, comment id 만 뽑아서 db쿼리를 날리자는 거였고,(score는 db 캐시를 해도 될 것 같긴 합니다.)
나머지 하나는 post:{postID}:comment:timestamp
이였는데 사실 create time에 따른 순서만 보장되면 된다면, sorted set이 아닌 그냥 list를 사용하는 게 메모리 절약 측에서는 좋아보입니다.
그리고 추가적인 기능으로 혹시 또 공감한 사람 공감 못하게 하기 위해서는 comment:{commentId}
set이 있으면 좋을 것 같다고 얘기한 거엿습니다!
앗 키를 post:{postID}:comment:score
이걸로 주자는 의미였나요??
키는 post:{postId}:like
이걸로 하되 score을 따로 주자는 말씀인줄 알았어요
나머지 하나는 post:{postID}:comment:timestamp이였는데 사실 create time에 따른 순서만 보장되면 된다면, sorted set이 아닌 그냥 list를 사용하는 게 메모리 절약 측에서는 좋아보입니다.
이 경우는 아래와 같이 저장하자는 말씀이신가요??
"post:{postID}:comment": [
{comment1 정보},
{comment2 정보}
]
이렇게 되면 그런데 댓글 update 요청을 처리 할때는 리스트를 다 가져와서 아이디에 맞는걸 찾아와야하는 것 같은데 비효율적이지 않을까요?!
"post:{postID}:comment": [ comment1ID, comment2ID, comment3ID]
를 원래 말했던 거였는데, 사실 시간 순 정렬은 최근 댓글 순으로 보여주는 거고 최근 댓글이 빨리 업데이트 될때만 캐시 용으로 잠깐 저장하면 되는 거라 저렇게 정보를 저장해도 될 것 같아요!
댓글 update 요청을 처리 할때는 리스트를 다 가져와서 아이디에 맞는걸 찾아와야하는 것 같은데
음 그래서 comment id들만 저장하고 실제 정보는 post:{postid}:comment:cache
같은 곳에 캐시용으로 저장하면 어떨까 생각했습니다.
id만 저장하면 update 시에는 id list가 바뀔 일이 없지 않나요? 아님 update하면 시간이 바뀌니 id도 바뀌나요?
아하 "post:{postID}:comment": [ comment1ID, comment2ID, comment3ID]
는 만료되지 않는 값이고
post:{postid}:comment:cache(?) 에 comment 정보가 있으면 레디스에서, 없으면 RDB에서 가져오는군요
혹시 post:{postid}:comment:cache
이건 어떻게 저장되는걸 생각하고 계신것인지 알 수 있을까요??
그럼 최종적으로 레디스에 저장될 정보
post 에 달린 댓글 id 모음 list
=> 만료되지 않는 정보
"post:{postID}:comment": [ comment1ID, comment2ID, comment3ID]
댓글에 공감한 사람 목록 set
=> 만료되지 않는 정보
"comment:{commentId}": [userID1, userID2]
공감수 sorted set
=> 만료되지 않는 정보
"post:{postID}:likes": [
1) 20 -> commentID1
2) 40 -> commentID3
3) 60 -> commentID2
]
업데이트 된 comments cache string
=> mysql 에 백업 후 지움
"comment:{commentID}:cache": {commentID에 해당하는 댓글 전체 정보}
가 맞을까요?
혹시
post:{postid}:comment:cache
이건 어떻게 저장되는걸 생각하고 계신것인지 알 수 있을까요??
ㅜㅜ제가 노트북을 꺼버려서 폰으로 쓰고 있느라 좀 느리네요.
그냥 정말 단순히 캐시의 의미로 쓴건데 comment:commentId:cache가 의미상 더 맞는 것 같아요.
최근 들어온 댓글들을 저런형태로 짧은 ttl과 함께 넣어주고, 공감을 많이 받은 댓글도 좀 도 긴 ttl과 함께 저런 키로 만들어 두면 db에 다녀오는 횟수가 좀 줄지 않을까 싶어서요.
그럼 최종적으로
네 맞습니다! 위에 언급한 캐시 빼고는 키 전체를 삭제하면 삭제했지 다 만료되지 않을 정보인 것 같아요
아하 이해했습니다!! 감사합니다 :)
저도 첨 설계해보는 거라 더 좋은 의견 생각나시년 언제든 코멘트 올려주세요!
기존 방식의 경우 게시물 당 comments 를 가져오기 쉽지 않은 설계인 것 같네요..