본문으로 건너뛰기

재화(코인)에 대한 기술 가이드

개요

배경

  • 게임에서 유저는 결제 또는 인게임 컨텐츠 및 운영 지급 등을 통해서 재화(보석, 골드, 젬과 같은 코인류)를 습득 및 사용할 수 있습니다.
    • 예) 각 결제 스토어에서 현금 결제, 게임 미션 달성 보상지급, 운영에서 출석 보상으로 우편함에 코인 지급
  • 이러한 재화는 유저가 어떤 방식으로 획득했는지를 구분해서 저장 및 관리 되어야하며 정책에서 정한 데로 차감되어야합니다.
    • 유저 환불이 필요할 때 유료 코인의 잔액만 확인이 가능해야 환불 금액을 산정 가능합니다.
    • 일본 자금 결제법에서는 일정 주기로 유료 잔액을 확인하고, 일정 금액 이상이라면 공탁금을 입금 해야 합니다.
    • 회계 규칙에 의해서 재화의 잔고 확인 및 증/차감 추적이 가능해야 합니다.
    • 일부 국가에서는 소비자 보호 등의 이유로 차감 순서가 틀릴 수 있습니다.
    • 정책 및 법은 변경될 수 있기에 개발은 가능하면 외부 변경에 유연하게 대응 할 수 있도록 설계 및 개발되면 좋습니다.
  • 게임 운영 과정에서 회수 기능이 필수입니다. 이때 유저의 코인 잔액은 음수가 될 수 있으니 꼭 감안하여 설계되어야 합니다.
    • 예) 게임 버그 등으로 재화 회수 → 이미 사용한 유저에게는 음수 값으로 차감(DB 음수 지원되는 sigined 타입 사용) → 유저가 결제 등으로 코인이 양수가 되면 정상 이용 가능(게임 내 UI에 음수 표시도 가능 해야함)

가이드의 한계

위험
  • DB 테이블 설계 내용은 참고를 위한 정보입니다.
  • 각 게임의 특성 및 로직을 모두 고려할 수 없기 때문에 참고하셔서 수정 사용하시면 됩니다.
  • 빌링 시스템에서 재화(코인) 관리를 직접 지원하지 않습니다. 참고하셔서 게임쪽에서 구현하셔야합니다.
    • 빌링 시스템에서 API로 지원하게되면 게임쪽의 연동 업무 범위가 너무 커집니다.
    • 또한, SP를 사용하는 등의 이유로 API를 호출하기 어려울 수 있고, 유저 응답 지연의 문제가 존재하기 때문에 게임에서 직접 구현합니다.
정보

총 2가지의 코인 관리 DB 설계를 제공하니 게임 특성에 따라 알맞은 방법을 골라 참고하여 DB 설계 부탁 드립니다.

  1. RDBMS
    1. #undefined-6
    2. #undefined-8
  2. NoSQL
    1. #id-1

코인 충전 형태에 따른 코드 정의

코드 정의 표

  • 오타 등의 문제로 실수 가능성이 높은 상황에서는 가능하면 영문 코드를 사용
    • 예) API 통신에는 영문 코드로 통신하고 DB에 저장할 때는 맵핑 정의에 따른 숫자 포맷으로 저장해서 DB 인덱스 사이즈를 줄임
      • 정합성 보장 등을 위해서 PK나 유니크 인덱스에 숫자 포맷 값으로 인덱스를 생성해두시는게 좋습니다.
영문 코드이름숫자포맷재무(회계) 유료 여부일본 자금결제법상 유료 여부설명
PAID유료1유료유료유저가 실제 돈을 주고 구입한 재화로 보너스는 제외
PAID_BONUS유료의 보너스2무료 (정책 변경될 수 있음)무료유저가 구입한 유료 코인에 덤으로 주는 보너스(자금결제법에서는 무료 처리해도 무방, 공탁금 줄일 수 있음)
PAID_INVEN유료인데 상품 보관함 용7유료무료상품 보관함에서 임시로 지급되는 유료 재화
PAID_INVEN_BONUS유료의 보너스인데 상품 보관함 용8유료무료상품 보관함에서 임시로 지급되는 덤 코인, 보너스 코인
FREE_BUY_PRODUCT무료 상품 구매14무료무료회계 및 자금결제법상 무료로 관리되는 '권리' 형태의 상품을 구입 후 충전할 때 사용 ' - 예) 로그인하면 무료 코인을 주는 상품 구입 -> 게임에서는 내일 로그인하면 해당 타입으로 코인 충전(단, BM에 구매 즉시 지급되는 코인을 추가하고 해당 코인은 유료으로 처리)
FREE_AD무료 광고19무료무료광고로 발생한 무료 재화. ' - 예)유니티 또는 구글 광고 SDK를 붙여서 BM을 준비할때. 광고를 보면 코인을 지급
FREE_OP무료 운영21무료무료푸시 및 쿠폰, 로그인 보상 등의 이유로 내부 운영 목적상에서 충전된 코인
FREE_SVC무료 서비스25무료무료게임내의 컨텐츠 요소로 발생한 충전된 무료 재화. ' - 예) 게임 던전 클리어시 무료 코인 지급
AUCTION_BIDDING경매장 입찰용31무료무료경매장 입찰에 사용되는 임시 코인(낙찰 시점에 유/무료 처리 등 고민해야할 부분이 있음)

코인 차감 순서

  • 유료 코인을 먼저 차감합니다. 이용약관 10조 5항 3호에도 관련 내용이 명시되어 있습니다.(2023-11-28 기준)
    • 유료 코인을 먼저 차감하지 않는다면 게임 종료, 환불 등의 이슈가 발생하면 개발사 및 회사는 큰 손해를 입을 수 있습니다.
      • 예) 운영 성격의 코인이 많이 지급되었는데 무료을 먼저 사용하게 된다면, 유저의 유료(유료) 코인 잔액은 차감이 안되고 계속 남아있어서 모두 환불 금액에 포함되어야하는 문제가 발생
정보

이용약관 유료 먼저 차감한다는 내용 발췌

③ 회원이 구매한 아이템은 획득 방식에 따라 유/무료 속성으로 구분됩니다. 회원이 유/무료 속성의 아이템을 모두 보유하고 있고 일부를 사용하였을 경우, 유료 속성이 우선 차감되고, 그 이후 무료 속성이 차감됩니다. 유료 속성만 보유 시에 차감 순서는 선입선출 방식(먼저 획득한 순서대로 차감되는 방식)에 따릅니다. 단, 사정에 따라 다르게 적용될 수 있으며 이 경우 서비스 내 또는 커뮤니티, 홈페이지 등을 통해 공지합니다.

코인 관리 DB 설계(참고용) - RDBMS

  • 아래 예제는 Mysql 기준으로 참고를 위해서 안내드리는 수준의 내용입니다.
  • 해당 row(또는 column)외 필요하신 추가 내용을 적용하셔도 무방합니다.
  • 다만, 꼭 관련 테이블들은 트랜잭션으로 묶어서 정합성에 문제가 생기지 않도록 해야합니다.
    • 이때 외부 API 호출 등 응답이 느린 로직이 있다면 트랜잭션 lock time이 길어지고, 다른 유저가 해당 데이터를 변경해야하는 로직이 있다면 데드락에 빠질 수 있으니 주의

(권장)확장성을 고려해서 충전 타입을 행 형태로 사용한 설계

  • 장점: 정책 또는 법규 등이 변경되어도 DB에 alter를 수행하지 않아도 되는 확장성의 장점
  • 단점: 유저의 코인 잔액을 조회하거나 변경할 때 여러 row를 읽어야하며 동시성에 대해서 더 잘 고민해야 함
    • 아래 샘플 내용은 참고를 위한 테이블 설계 및 쿼리입니다. 상황에 맞춰서 테이블명과 컬럼명 변경, 인덱스 조정 등이 진행되어도 괜찮습니다. 다만 증/차감 누락이나 중복 충전 등 크리티컬한 문제는 주의해주세요.

테이블 DDL 샘플

-- 유저의 코인 잔액 테이블(마스터 테이블)
CREATE TABLE `coin_balance`
(
`player_id` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '플레이어ID(유저ID)',
`coin_cd` VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '코인 종류(GEM 등)',
`charge_type` TINYINT UNSIGNED NOT NULL COMMENT '코인의 충전타입 구분(유상,무상, 보너스 등)',
`balance` BIGINT NOT NULL COMMENT '코인의 현재 잔액(운영상의 이슈로 마이너스 잔액 지원 되어야함. 예. 버그 악용 유저의 재화 회수. 마이너스 통장 개념)',
`created_at` TIMESTAMP NOT NULL COMMENT '최초 저장 일시',
`updated_at` TIMESTAMP NOT NULL COMMENT '변경일시(최초에는 저장될때와 같은 일시로 저장)',
PRIMARY KEY (`player_id`, `coin_cd`, `charge_type`)
) ENGINE = INNODB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_unicode_ci COMMENT ='유저의 현재 코인(재화)의 잔액 관리 테이블(charge_type이 늘어날 수 있음을 감안하여 설계)'
;
-- 유저별 일자별 코인 증/차감 데이터(단, 해당일에 증/차감이 발생하지 않은 유저의 데이터는 존재하지 않음)
CREATE TABLE `coin_balance_daily`
(
`target_ymd` DATE NOT NULL COMMENT '대상일',
`player_id` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '플레이어ID(유저ID)',
`coin_cd` VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '코인 종류(GEM 등)',
`charge_type` TINYINT UNSIGNED DEFAULT NULL COMMENT '코인의 충전타입 구분(유상,무상, 보너스 등)',
`gain_coin` BIGINT UNSIGNED DEFAULT NULL COMMENT '충전으로 획득한 코인',
`use_coin` BIGINT UNSIGNED DEFAULT NULL COMMENT '사용하게되어 소모된 코인',
`balance` BIGINT NOT NULL COMMENT '잔액',
`created_at` TIMESTAMP NOT NULL COMMENT '최초 저장일시',
`updated_at` TIMESTAMP NOT NULL COMMENT '변경일시(최초에는 저장될때와 같은 일시로 저장)',
PRIMARY KEY (`target_ymd`, `player_id`, `coin_cd`)
) ENGINE = INNODB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_unicode_ci COMMENT ='유저의 일자별 코인 증차감 통계 데이터(재화 증차감 발생할 때 같은 트랜잭션내에 같이 저장)'
;
CREATE TABLE `coin_balance_hist`
(
`hist_id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'PK',
`req_id` VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '요청ID(중복 요청 방어 목적 및 추적용)',
`player_id` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '유저ID',
`coin_cd` VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '코인 종류(GEM 등)',
`charge_type` TINYINT UNSIGNED NOT NULL COMMENT '코인의 충전타입 구분(유상,무상, 보너스 등)',
`player_country` VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '유저의 국가정보(국가별 법률 준수 목적으로 필요시 저장)',
`amount` INT NOT NULL COMMENT '증감되는 변경 양',
`balance` BIGINT NOT NULL COMMENT '재화 잔액',
`balance_change_type` ENUM ('PLUS','MINUS') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '잔액 변경 종류(증/차감)',
`reason` VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '증감되는 사유',
`memo` VARCHAR(300) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '메모(필요시 저장)',
`hist_created_at` TIMESTAMP NOT NULL COMMENT '이력저장일시',
PRIMARY KEY (`hist_id`),
UNIQUE KEY `IDX_reqId_coinCd_chargeType` (`req_id`, `coin_cd`, `charge_type`),
KEY `IDX_playerId_histCreatedAt` (`player_id`, `hist_created_at`)
) ENGINE = INNODB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_unicode_ci COMMENT ='유저의 재화 잔액 변경 이력(N개의 charge type에 증/차감이 발생하면 N개의 이력이 한번에 insert될 수 있으며 이때의 req_id는 같은 값)'
;

참고용 쿼리 샘플

-- 유저의 특정 코인 총 잔액을 조회(charge_type을 제외하고 group by 후 sum)
SELECT coin_cd AS coinCd,
SUM(balance) AS balance
FROM coin_balance
WHERE player_id = 'playerId'
AND coin_cd = 'GEM'
GROUP BY coin_cd
ORDER BY NULL
;
-- 차감을 위한 잔액 조회 쿼리(charge_type에 따른 차감 순서를 정의하고 싶으면, DB쿼리의 charge_type 필드를 대상으로 소팅해서 조회 후 처리하거나 로직에서 소팅해서 처리)
-- FIELD기능을 꼭 사용할 필요 없으며, 사용하게되면 하드코딩 안하도록 주의
SELECT charge_type AS chargetType,
coin_cd AS coinCd,
balance AS balance
FROM coin_balance
WHERE player_id = 'playerId'
AND coin_cd = 'GEM'
ORDER BY FIELD(chargetType, 1, 2, 7, 8, 14, 19, 21, 25, 31) ASC
;
-- GEM이라는 코인에 대해서 유료 충전. DB로 동시성 이슈에 대한 방어 및 트랜잭션 처리를 명확하게 하기위해 upsert쿼리를 사용해서 insert(최초 추가) 또는 update(증가) 처리
INSERT INTO `coin_balance`
(`player_id`,
`coin_cd`,
`charge_type`,
`balance`,
`created_at`,
`updated_at`)
VALUES ('playerId',
'GEM',
7,
100,
NOW(),
NOW())
ON DUPLICATE KEY
UPDATE balance = balance + 100,
updated_at = NOW()
;
-- 차감
-- update 쿼리로 차감하되 동시성 이슈를 막기 위해서 현재 잔액 조회를 다른 쿼리에서 진행하지않고, 같은 쿼리에서 처리
)
UPDATE
user_coin_balance_#{tableShardNo}
SET
balance = balance - (#{amount}),
블라블라
WHERE 블라블라
-----
기타 쿼리는 생략

단순화된 열 형태의 설계

  • 여러 코인 충전 형태 중 필수인 일부 충전 방식에 대해서 컬럼 기반으로 관리
  • 1개의 행(DB row)에 충전 타입에 따른 컬럼을 개별적으로 추가해서 잔액을 관리
    • 유료, 무료, 유료 보너스 3가지 구분은 필수
    • 아래 샘플 내용은 참고를 위한 테이블 설계 및 쿼리입니다. 상황에 맞춰서 테이블명과 컬럼명 변경, 인덱스 조정 등이 진행되어도 괜찮습니다. 다만 증/차감 누락이나 중복 충전 등 크리티컬한 문제는 주의해주세요.
정보

개발은 쉽지만 확장성이 부족한 열(컬럼) 잔액을 관리하는 DB 설계

테이블 DDL 샘플


CREATE TABLE `coin_balance_column_type`
(
`player_id` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '플레이어ID(유저ID)',
`coin_cd` VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '코인 종류(GEM 등)',
`balance_paid` BIGINT NOT NULL COMMENT '유료 코인의 현재 잔액',
`balance_paid_bonus` BIGINT NOT NULL COMMENT '유료 보너스 코인의 현재 잔액',
`balance_free` BIGINT NOT NULL COMMENT '무료 코인의 현재 잔액',
`created_at` TIMESTAMP NOT NULL COMMENT '최초 저장 일시',
`updated_at` TIMESTAMP NOT NULL COMMENT '변경일시(최초에는 저장될때와 같은 일시로 저장)',
PRIMARY KEY (`player_id`, `coin_cd`)
) ENGINE = INNODB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_unicode_ci COMMENT ='유저의 현재 코인(재화)의 잔액 관리 테이블(간소화 버전)'
;

CREATE TABLE `coin_balance_daily_column_type`
(
`target_ymd` DATE NOT NULL COMMENT '대상일',
`player_id` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '플레이어ID(유저ID)',
`coin_cd` VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '코인 종류(GEM 등)',
`gain_coin` BIGINT UNSIGNED DEFAULT NULL COMMENT '충전으로 획득한 코인',
`use_coin` BIGINT UNSIGNED DEFAULT NULL COMMENT '사용하게되어 소모된 코인',
`balance_paid` BIGINT NOT NULL COMMENT '유료 코인의 현재 잔액',
`balance_paid_bonus` BIGINT NOT NULL COMMENT '유료 보너스 코인의 현재 잔액',
`balance_free` BIGINT NOT NULL COMMENT '무료 코인의 현재 잔액',
`created_at` TIMESTAMP NOT NULL COMMENT '최초 저장일시',
`updated_at` TIMESTAMP NOT NULL COMMENT '변경일시(최초에는 저장될때와 같은 일시로 저장)',
PRIMARY KEY (`target_ymd`, `player_id`, `coin_cd`)
) ENGINE = INNODB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_unicode_ci COMMENT ='유저의 일자별 코인 증차감 통계 데이터(재화 증차감 발생할 때 같은 트랜잭션내에 같이 저장)'
;

CREATE TABLE `coin_balance_hist_column_type`
(
`hist_id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'PK',
`req_id` VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '요청ID(중복 요청 방어 목적 및 추적용)',
`player_id` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '유저ID',
`coin_cd` VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '코인 종류(GEM 등)',
`player_country` VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '유저의 국가정보(국가별 법률 준수 목적으로 필요시 저장)',
`amount` INT NOT NULL COMMENT '증감되는 변경 양',
`balance_paid` BIGINT NOT NULL COMMENT '유료 코인의 현재 잔액',
`balance_paid_bonus` BIGINT NOT NULL COMMENT '유료 보너스 코인의 현재 잔액',
`balance_free` BIGINT NOT NULL COMMENT '무료 코인의 현재 잔액',
`balance_change_type` ENUM ('PLUS','MINUS') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '잔액 변경 종류(증/차감)',
`reason` VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '증감되는 사유',
`memo` VARCHAR(300) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '메모(필요시 저장)',
`hist_created_at` TIMESTAMP NOT NULL COMMENT '이력저장일시',
PRIMARY KEY (`hist_id`),
UNIQUE KEY `IDX_reqId_coinCd` (`req_id`, `coin_cd`),
KEY `IDX_playerId_histCreatedAt` (`player_id`, `hist_created_at`)
) ENGINE = INNODB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_unicode_ci COMMENT ='유저의 재화 잔액 변경 이력'

참고용 쿼리 샘플

  • row 방식 쿼리를 참고하되 charge_type에 대해서 행을 열로 변경한 부분을 고려해서 수정해서 사용

코인 관리 DB 설계(참고용) - NoSQL

단순화된 열 형태의 설계

  • 여러 코인 충전 형태 중 필수인 일부 충전 방식에 대해서 필드 기반으로 관리
  • 1개의 행(Document)에 충전 타입에 따른 필드를 개별적으로 추가해서 잔액을 관리
    • 유료, 무료, 유료 보너스 3가지 구분은 필수
    • 샘플 내의 내용은 참고를 위한 내용입니다. 상황에 맞춰서 컬렉션(필드 및 인덱스 등)은 변경되어도 괜찮습니다. 다만 증/차감 누락이나 중복 충전 등 크리티컬한 문제는 주의해주세요.

샘플 컬렉션 정의 - coin_balance

  • 유저의 현재 코인(재화)의 잔액을 관리하는 마스터 컬렉션
  • 재화의 증/차감 변경이 발생할 때마다 데이터가 insert 또는 update되어 저장되는 컬렉션
  • 코인 코드에 따라서 유저는 N개의 Document를 보유할 수 있음

필드 정의

필드데이터 타입설명
_idobject자동 생성되는 objectId 필드로 개발자가 생성할 수도 있지만 일반적으로 드라이버에서 생성됨6698b68bd8cd923a387c3572
player_idstring플레이어ID(유저ID)test_user
coin_cdstring코인 종류(GEM 등)GEM
balance_paidnumber유료 코인의 현재 잔액5
balance_paid_bonusnumber유료 보너스 코인의 현재 잔액3
balance_freenumber무료 코인의 현재 잔액0
created_atobject최초 저장 일시ISODate("2024-07-18T06:43:44.099Z")
updated_atobject변경일시(최초에는 저장될때와 같은 일시로 저장)ISODate("2024-07-18T06:43:44.099Z")

상세 참고사항

- 유저의 코인 잔액 컬렉션(마스터)

1. 컬렉션 생성
db.createCollection('coin_balance');

2. 인덱스 생성(유니크)
-- 유니크 인덱스를 생성하여 데이터 정합성 문제를 방지
db.coin_balance.createIndex( { player_id:1, coin_cd:1}, { unique: true } );

3. 테스트용 커맨드
-- 신규 충전 insert
db.coin_balance.insert( {"player_id":"test_user", "coin_cd":"GEM", "balance_paid":5, "balance_paid_bonus":3, "balance_free":0, "created_at": new Date(), "updated_at": new Date()} );
db.coin_balance.insert( {"player_id":"test_user", "coin_cd":"GOLD", "balance_paid":5, "balance_paid_bonus":3, "balance_free":0, "created_at": new Date(), "updated_at": new Date()} );

-- find결과(필수 필드들 참고)
> db.coin_balance.find().pretty()
{
"_id" : ObjectId("6698b9a0d8cd923a387c3574"),
"player_id" : "test_user",
"coin_cd" : "GEM",
"balance_paid" : 5,
"balance_paid_bonus" : 3,
"balance_free" : 0,
"created_at" : ISODate("2024-07-18T06:43:44.099Z"),
"updated_at" : ISODate("2024-07-18T06:43:44.099Z")
}
;

-- 데이터 타입 확인을 위한 커맨드
var doc = db.coin_balance.findOne({ _id: ObjectId("6698b9a0d8cd923a387c3574") });
for (var key in doc) {
print(key + ": " + typeof doc[key]);
}
;

-- 데이터 타입 확인 결과 내용 샘플
_id: object
player_id: string
coin_cd: string
balance_paid: number
balance_paid_bonus: number
balance_free: number
created_at: object
updated_at: object

컬렉션 정의 - coin_balance_hist

  • 유저의 재화 잔액 변경 이력을 저장하는 컬렉션
  • 재화의 증/차감 변경이 발생할 때마다 데이터가 insert되어 저장되는 컬렉션(Update가 발생하면 안됨)

필드 정의

필드데이터 타입설명
_idobject자동 생성되는 objectId 필드로 개발자가 생성할 수도 있지만 일반적으로 드라이버에서 생성됨6698b68bd8cd923a387c3572
req_idstring요청ID(중복 요청 방어 목적 및 추적용)uuid-player_id-balance-plus-01
player_idstring플레이어ID(유저ID)test_user
coin_cdstring코인 종류(GEM 등)GEM
player_countrystring유저의 국가정보(국가별 법률 준수 목적으로 필요시 저장)KR
amountnumber증감되는 변경 양5
balance_paidnumber유료 코인의 현재 잔액5
balance_paid_bonusnumber유료 보너스 코인의 현재 잔액3
balance_freenumber무료 코인의 현재 잔액0
balance_change_typestring잔액 변경 종류(증/차감) PLUS 또는 MINUS,PLUS
reasonstring증감되는 사유plus_test
memostring메모(필요시 저장)memo_test
hist_created_atobject이력저장일시ISODate("2024-07-18T07:54:53.388Z")

상세 참고 사항

-- 유저의 재화 잔액 변경 이력을 저장하는 컬렉션(이력 컬렉션)

1. 컬렉션 생성
db.createCollection('coin_balance_hist');

2. 인덱스 생성
-- req_id를 이용한 유니크 인덱스로 데이터 오류 방지
db.coin_balance_hist.createIndex( { req_id:1, coin_cd:1}, { unique: true } );
db.coin_balance_hist.createIndex( { player_id:1, hist_created_at:-1});

3. 테스트용 커맨드
-- 신규 이력 insert
db.coin_balance_hist.insert(
{
"req_id":"uuid-player_id-balance-plus-01",
"player_id":"test_user",
"coin_cd":"GEM",
"player_country":"KR",
"amount":5,
"balance_paid":5,
"balance_paid_bonus":3,
"balance_free":0,
"balance_change_type":"PLUS",
"reason":"plus_test",
"memo":"memo_test",
"hist_created_at":new Date()
}
);

-- find결과(필수 필드들 참고)
> db.coin_balance_hist.find().pretty()
{
"_id" : ObjectId("6698ca4dd8cd923a387c3578"),
"req_id" : "uuid-player_id-balance-plus-01",
"player_id" : "test_user",
"coin_cd" : "GEM",
"player_country" : "KR",
"amount" : 5,
"balance_paid" : 5,
"balance_paid_bonus" : 3,
"balance_free" : 0,
"balance_change_type" : "PLUS",
"reason" : "plus_test",
"memo" : "memo_test",
"hist_created_at" : ISODate("2024-07-18T07:54:53.388Z")
}


-- 데이터 타입 확인을 위한 커맨드
var doc = db.coin_balance_hist.findOne({ _id: ObjectId("6698ca4dd8cd923a387c3578") });
for (var key in doc) {
print(key + ": " + typeof doc[key]);
}


-- 데이터 타입 확인 결과 내용 샘플
_id: object
req_id: string
player_id: string
coin_cd: string
player_country: string
amount: number
balance_paid: number
balance_paid_bonus: number
balance_free: number
balance_change_type: string
reason: string
memo: string
hist_created_at: object