DB 분리는 기존 데이터베이스 ( MySQL ) 의 설계문제 및 정규화, 데이터 무결성등의 문제가 레거시로 존재하기에 데이터를 분리하여 최적의 퍼포먼스 및 유지보수를 고려하여 결정
FEED 의 데이터는 SNS 와 유사한 서비스이므로 SNS 형식과 비슷한 구조를 가진다.
피드 예시
DB 분리는 기존 데이터베이스 ( MySQL ) 의 설계문제 및 정규화, 데이터 무결성등의 문제가 레거시로 존재하기에 데이터를 분리하여 최적의 퍼포먼스 및 유지보수를 고려하여 결정
FEED 의 데이터는 SNS 와 유사한 서비스이므로 SNS 형식과 비슷한 구조를 가진다.
피드 예시

피드는 위와 같은 SNS 형식으로 서비스 된다.
데이터는 React 에서 사용하는데 용이하도록 json 형식의 데이터를 API 형태로 반환하도록 한다.
그렇다면 기존 데이터베이스와 같이 MySQL 에 저장하는 것이 옳은가?
json 형식으로 반환하기 위해서는 json 형식으로 저장되는 것이 가장 좋다.
MySQL에서 JSON 타입의 컬럼은 내부적으로 문자열 형식으로 저장된다. 그러나 이 문자열은 JSON 형식의 데이터를 포함하고 있으며, MySQL은 이 데이터를 JSON 객체로 처리할 수 있다. 즉, 저장될 때는 텍스트 형식으로 저장되지만, 이를 이용한 쿼리나 조작은 JSON 객체로서 수행된다.
이것이 의미하는 것은 json 방식을 처리하기 위한 조작은 문자열을 꺼내어와서 json 객체로 변환하고 데이터가 변환하였을 때 다시 문자열로 변환하여 저장한다는 점이다.
FEED는 FEED 자체의 업데이트가 많지 않다고 판단하였고
따라서 현재 개발자의 러닝커브가 적으며 json 포멧의 객체를 효율적으로 쓰기 위해 postgreSQL 을 선택했다.
다음은 postgreSQL 의 타 데이터베이스와의 장단점을 비교한 자료이다.
json과 jsonb 두 가지 JSON 데이터 타입을 제공합니다. json은 단순한 텍스트 저장소이고, jsonb는 바이너리 형식으로 저장되어 빠른 조회 및 수정이 가능합니다.jsonb 타입의 데이터를 위한 GIN (Generalized Inverted Index) 및 B-tree 인덱스를 생성할 수 있어, 데이터 검색 성능을 크게 향상시킬 수 있습니다.jsonb 타입은 더 효율적인 저장과 인덱싱을 제공합니다. PostgreSQL은 더 강력한 JSON 관련 함수를 제공합니다.PostgreSQL은 JSON 데이터 타입을 효과적으로 지원하면서도 전통적인 관계형 데이터베이스의 장점을 유지하고 있어, JSON 데이터를 많이 사용하는 애플리케이션에 매우 유용한 선택이 될 수 있습니다.

피드는 위와 같은 SNS 형식으로 서비스 된다.
데이터는 React 에서 사용하는데 용이하도록 json 형식의 데이터를 API 형태로 반환하도록 한다.
그렇다면 기존 데이터베이스와 같이 MySQL 에 저장하는 것이 옳은가?
json 형식으로 반환하기 위해서는 json 형식으로 저장되는 것이 가장 좋다.
MySQL에서 JSON 타입의 컬럼은 내부적으로 문자열 형식으로 저장된다. 그러나 이 문자열은 JSON 형식의 데이터를 포함하고 있으며, MySQL은 이 데이터를 JSON 객체로 처리할 수 있다. 즉, 저장될 때는 텍스트 형식으로 저장되지만, 이를 이용한 쿼리나 조작은 JSON 객체로서 수행된다.
이것이 의미하는 것은 json 방식을 처리하기 위한 조작은 문자열을 꺼내어와서 json 객체로 변환하고 데이터가 변환하였을 때 다시 문자열로 변환하여 저장한다는 점이다.
FEED는 FEED 자체의 업데이트가 많지 않다고 판단하였고
따라서 현재 개발자의 러닝커브가 적으며 json 포멧의 객체를 효율적으로 쓰기 위해 postgreSQL 을 선택했다.
다음은 postgreSQL 의 타 데이터베이스와의 장단점을 비교한 자료이다.
json과 jsonb 두 가지 JSON 데이터 타입을 제공합니다. json은 단순한 텍스트 저장소이고, jsonb는 바이너리 형식으로 저장되어 빠른 조회 및 수정이 가능합니다.jsonb 타입의 데이터를 위한 GIN (Generalized Inverted Index) 및 B-tree 인덱스를 생성할 수 있어, 데이터 검색 성능을 크게 향상시킬 수 있습니다.jsonb 타입은 더 효율적인 저장과 인덱싱을 제공합니다. PostgreSQL은 더 강력한 JSON 관련 함수를 제공합니다.PostgreSQL은 JSON 데이터 타입을 효과적으로 지원하면서도 전통적인 관계형 데이터베이스의 장점을 유지하고 있어, JSON 데이터를 많이 사용하는 애플리케이션에 매우 유용한 선택이 될 수 있습니다.
피드의 데이터 형식을 다음과 같이 정의한다.
{
"id": "피드아이디",
"feed": "피드의 버전정보(삭제가능)",
"feed_data": {
"피드의 데이터": "값"
},
"celeb_data": {
"피드를 올린 셀럽의 데이터": "값"
},
"product_data": [
{
"피드에 등록(태그)된 상품의 데이터": "값"
},
{
"피드에 등록(태그)된 상품의 데이터": "값"
}
],
"feed_like" : {
total_like : 3,
"likes" : [
{
"like_id : 1,
"mb_id" : "user1",
"reg_date" : "2024-07-03 12:34:56"
},
{
"like_id : 2,
"mb_id" : "user2",
"reg_date" : "2024-07-03 19:47:32"
}
]
},
"feed_comment": {
"total_comments": 7, // delete_yn이 'n'인 댓글의 총 개수
"comments": [
{
"comment_id": 1,
"mb_id": "user1",
"comment": "This is a comment.",
"reg_date": "2024-07-03 12:34:56",
"delete_yn": "n",
"replies": [
{
"comment_id": 2,
"mb_id": "user2",
"comment": "This is a reply.",
"reg_date": "2024-07-03 13:45:67",
"delete_yn": "n",
"replies": []
}
]
},
{
"comment_id": 3,
"mb_id": "user3",
"comment": "This is another comment.",
"reg_date": "2024-07-03 14:56:78",
"delete_yn": "y",
"replies": []
}
]
},
"beauty_reviews": [
{
"review_content": null,
"review_info_usage": null,
"review_info_option": null
}
]
}
다음은 실제 데이터의 예제이다. ( product_data 확인요망 & feed_like , feed_comment 추가해야함 )
{
"id": 24452,
"feed": "ver1",
"feed_data": {
"tag": "#misope",
"feel": "",
"cimg1": "../_data/tmp/1719302238__6732.jpg",
"cimg2": "../_data/tmp/1719302242__5792.jpg",
"cimg3": "../_data/tmp/1719302242__4241.jpg",
"cimg4": "../_data/tmp/1719302242__7108.jpg",
"cimg5": "../_data/tmp/1719302242__3080.jpg",
"cimg6": "",
"cimg7": "",
"cimg8": "",
"cimg9": "",
"mb_id": "naver_872c0866",
"style": "페미닌,캐주얼/내추럴",
"cimg10": "",
"private": "0",
"product": "direct0_667a78ec39bdc",
"category": "우먼|디자이너_우먼",
"contents": "너무 예뻐버려,, #misope ✨",
"reg_date": "2024-06-25 16:59:40",
"upt_date": "",
"cdn_check": 0,
"top_fixed": 0,
"cdn_check3": 0,
"google_api": "",
"x_position": "|",
"y_position": "|",
"update_date": "2024-06-25 17:00:02",
"top_fixed_order": 0,
"feed_location_id": null
},
"celeb_data": {
"job": "모델",
"form": "역삼각형",
"height": 163,
"mb_sex": "F",
"weight": 46,
"mb_nick": "유여니",
"category": "우먼|디자이너_우먼",
"mb_birth": "19931102",
"mb_leave": 0,
"mb_level": 5,
"top_size": "S",
"foot_size": "230",
"mb_gender": "여성",
"birth_year": "1996",
"celeb_kids": 0,
"kids_style": null,
"bottom_size": "26",
"celeb_style": "페미닌|러블리|스포티|키치룩|캐주얼/내추럴",
"kids_height": null,
"kids_weight": null,
"target_age1": 20,
"target_age2": 35,
"as_recommend": 0,
"kids_top_size": null,
"kids_foot_size": null,
"mb_celeb_grade": 2,
"kids_target_age": null,
"mb_unlogin_date": "",
"kids_bottom_size": null
},
"product_data": [
{
"bn_id": "65234",
"item_idx": "direct0_667a78ec39bdc",
"item_name": " 012424028 7cm",
"brand_name": "misope",
"real_mb_id": "naver_872c0866",
"view_count": "0",
"cate_depth1": "우먼",
"cate_depth2": "신발",
"cate_depth3": "샌들",
"cate_depth4": "",
"cate_depth5": "",
"category_name": "샌들",
"brand_entry_count": "0"
},
{
"bn_id": "65234",
"item_idx": "direct0_667a78ec39bdc",
"item_name": " 012424028 7cm",
"brand_name": "misope",
"real_mb_id": "naver_872c0866",
"view_count": "0",
"cate_depth1": "우먼",
"cate_depth2": "신발",
"cate_depth3": "샌들",
"cate_depth4": "",
"cate_depth5": "",
"category_name": "샌들",
"brand_entry_count": "0"
}
],
"feed_like" : {
total_like : 3,
"likes" : [
{
"like_id : 1,
"mb_id" : "user1",
"reg_date" : "2024-07-03 12:34:56"
},
{
"like_id : 2,
"mb_id" : "user2",
"reg_date" : "2024-07-03 19:47:32"
}
]
},
"feed_comment": {
"total_comments": 7, // delete_yn이 'n'인 댓글의 총 개수
"comments": [
{
"comment_id": 1,
"mb_id": "user1",
"comment": "This is a comment.",
"reg_date": "2024-07-03 12:34:56",
"delete_yn": "n",
"replies": [
{
"comment_id": 2,
"mb_id": "user2",
"comment": "This is a reply.",
"reg_date": "2024-07-03 13:45:67",
"delete_yn": "n",
"replies": []
}
]
},
{
"comment_id": 3,
"mb_id": "user3",
"comment": "This is another comment.",
"reg_date": "2024-07-03 14:56:78",
"delete_yn": "y",
"replies": []
}
]
},
"beauty_reviews": [
{
"review_content": null,
"review_info_usage": null,
"review_info_option": null
}
]
}
데이터 형식이 아래와 같을 때 다음과 같은 쿼리를 예상할 수 있다.
다음은 예상되는 쿼리의 예제코드이다.
WITH updated_feed AS (
SELECT feed
FROM feed_data
WHERE id = 24452
)
UPDATE feed_data
SET feed = jsonb_set(
feed,
'{feed_comment,comments}',
(feed->'feed_comment'->'comments')::jsonb || jsonb_build_object(
'comment_id', 4, -- 새로운 comment_id
'mb_id', 'user4',
'comment', 'New comment',
'reg_date', to_jsonb(NOW()), -- 현재 시간 삽입
'delete_yn', 'n',
'replies', '[]'::jsonb
)
),
feed = jsonb_set(
feed,
'{feed_comment,total_comments}',
((feed->'feed_comment'->>'total_comments')::int + 1)::text::jsonb
)
FROM updated_feed
WHERE id = 24452;
WITH updated_feed AS (
SELECT feed
FROM feed_data
WHERE id = 24452
)
UPDATE feed_data
SET feed = jsonb_set(
feed,
'{feed_comment,comments}',
(SELECT jsonb_agg(
CASE
WHEN value->>'comment_id' = '1' THEN
jsonb_set(value, '{delete_yn}', '"y"')
ELSE value
END
)
FROM jsonb_array_elements(feed->'feed_comment'->'comments') value)
),
feed = jsonb_set(
feed,
'{feed_comment,total_comments}',
((feed->'feed_comment'->>'total_comments')::int - 1)::text::jsonb
)
FROM updated_feed
WHERE id = 24452;
WITH updated_feed AS (
SELECT feed
FROM feed_data
WHERE id = 24452
)
UPDATE feed_data
SET feed = jsonb_set(
feed,
'{feed_comment,comments}',
(SELECT jsonb_agg(
CASE
WHEN value->>'comment_id' = '1' THEN
jsonb_set(value, '{comment}', '"Updated comment"')
ELSE value
END
)
FROM jsonb_array_elements(feed->'feed_comment'->'comments') value)
)
FROM updated_feed
WHERE id = 24452;
WITH updated_feed AS (
SELECT feed
FROM feed_data
WHERE id = 24452
)
UPDATE feed_data
SET feed = jsonb_set(
feed,
'{feed_like,likes}',
(feed->'feed_like'->'likes')::jsonb || jsonb_build_object(
'like_id', 3, -- 새로운 like_id
'mb_id', 'user3',
'reg_date', to_jsonb(NOW()) -- 현재 시간 삽입
)
),
feed = jsonb_set(
feed,
'{feed_like,total_like}',
((feed->'feed_like'->>'total_like')::int + 1)::text::jsonb
)
FROM updated_feed
WHERE id = 24452;
WITH updated_feed AS (
SELECT feed
FROM feed_data
WHERE id = 24452
)
UPDATE feed_data
SET feed = jsonb_set(
feed,
'{feed_like,likes}',
(SELECT jsonb_agg(value)
FROM jsonb_array_elements(feed->'feed_like'->'likes') value
WHERE value->>'like_id' <> '1')
),
feed = jsonb_set(
feed,
'{feed_like,total_like}',
((feed->'feed_like'->>'total_like')::int - 1)::text::jsonb
)
FROM updated_feed
WHERE id = 24452;
WITH updated_feed AS (
SELECT feed
FROM feed_data
WHERE id = 24452
)
UPDATE feed_data
SET feed = jsonb_set(
feed,
'{feed_like,likes}',
(SELECT jsonb_agg(
CASE
WHEN value->>'like_id' = '1' THEN
jsonb_set(value, '{reg_date}', to_jsonb(NOW()))
ELSE value
END
)
FROM jsonb_array_elements(feed->'feed_like'->'likes') value)
)
FROM updated_feed
WHERE id = 24452;
각 컬럼으로 구별된 테이블 스키마 예제
CREATE TABLE feed_data (
id SERIAL PRIMARY KEY,
feed JSONB,
feed_data JSONB,
celeb_data JSONB,
product_data JSONB[], -- 여러 개의 상품 데이터를 배열로 저장
feed_like JSONB[], -- 여러 개의 좋아요 데이터를 배열로 저장
feed_comment JSONB[], -- 여러 개의 댓글 데이터를 배열로 저장
beauty_reviews JSONB[] -- 여러 개의 리뷰 데이터를 배열로 저장
);
INSERT INTO feed_data (feed, feed_data, celeb_data, product_data, feed_like, feed_comment, beauty_reviews)
VALUES (
블라불라
);
DELETE FROM feed_data
WHERE id = 24452;
sql코드 복사
UPDATE feed_data
SET feed = jsonb_set(feed, '{tag}', '"#newtag"'),
feed_data = jsonb_set(feed_data, '{contents}', '"Updated contents"'),
celeb_data = jsonb_set(celeb_data, '{mb_nick}', '"Updated nickname"'),
product_data = jsonb_set(product_data, '[0].item_name', '"Updated item"'),
feed_like = jsonb_set(feed_like, '[0].mb_id', '"updated_user1"'),
feed_comment = jsonb_set(feed_comment, '[0].comment', '"Updated comment"'),
beauty_reviews = jsonb_set(beauty_reviews, '[0].review_content', '"Updated review"')
WHERE id = 24452;
INSERT INTO celeb_data (job, mb_nick, mb_sex, height)
VALUES ('모델', '유여니', 'F', 163);
DELETE FROM celeb_data
WHERE mb_nick = '유여니';
UPDATE celeb_data
SET job = '배우',
height = 170
WHERE mb_nick = '유여니';
INSERT INTO product_data (data)
VALUES ('[{"bn_id": "65234", "item_name": "012424028 7cm", "category_name": "샌들"}]');
DELETE FROM product_data
WHERE item_name = '012424028 7cm';
UPDATE product_data
SET data = jsonb_set(data, '[0].category_name', '"샌들 수정"')
WHERE item_name = '012424028 7cm';
INSERT INTO feed_like (data)
VALUES ('[{"mb_id": "user1", "reg_date": "2024-07-03 12:34:56"}, {"mb_id": "user2", "reg_date": "2024-07-03 19:47:32"}]');
DELETE FROM feed_like
WHERE mb_id = 'user1';
UPDATE feed_like
SET data = jsonb_set(data, '[0].reg_date', '"2024-07-04 08:30:00"')
WHERE mb_id = 'user1';
INSERT INTO feed_comment (data)
VALUES ('[{"mb_id": "user1", "comment": "This is a comment.", "reg_date": "2024-07-03 12:34:56", "delete_yn": "n", "replies": []}, {"mb_id": "user3", "comment": "Another comment.", "reg_date": "2024-07-03 14:56:78", "delete_yn": "n", "replies": []}]');
DELETE FROM feed_comment
WHERE mb_id = 'user1';
UPDATE feed_comment
SET data = jsonb_set(data, '[0].comment', '"Updated comment"')
WHERE mb_id = 'user1';
INSERT INTO beauty_reviews (data)
VALUES ('[{"review_content": "Great product!", "review_info_usage": "Good for daily use.", "review_info_option": "Love it!"}]');
DELETE FROM beauty_reviews
WHERE review_content = 'Great product!';
UPDATE beauty_reviews
SET data = jsonb_set(data, '[0].review_content', '"Updated review"')
WHERE review_content = 'Great product!';