Spring Boot에서 MongoTemplate를 활용한 MongoDB 쿼리 자유롭게 사용하기
MongoRepository를 사용하다 쿼리 작성해 한계가 있어서 좀 더 고도화된 쿼리문을 작성할 수 있는
MongoTemplate 사용하기로 하였습니다. MongoTemplate을 사용하기 위해서는 db.collection.find()와 같은 MongoDB 쿼리문도 어느 정도 알아야했습니다. 제가 원하는 쿼리 결과를 생각하면서 쿼리문 예제와 docs 를 찾아보며 연구를 하고 원하는 결과가 나오는 것을 보니 재미가 있더라구요!
이번 포스팅은 에서 사용해봤던 쿼리들을 통해
MongoDB 쿼리문과 MongoTemplate 쿼리 작성을 비교해보면서
쿼리를 자유롭게 작성한 것을 공유할 것입니다.
1. 몽고디비 세팅
1.1. MongoDB 설치
Download MongoDB Community Server | MongoDB
1.2. 환경변수설정
Path에 bin 경로 추가
C:\Program Files\MongoDB\Server\6.0\bin
1.3. MongoDB Compass 설치
Try MongoDB Tools - Download Free Here | MongoDB
1.4. MongoDB 쉘
윈도우 cmd로 쉘이 안돼서 몽고db툴 중에 Shell을 다운받아 사용
Try MongoDB Tools - Download Free Here | MongoDB
2. 몽고DB 와 스프링부트 연동
2.1. 몽고db 설정
application.yml
spring:
data:
mongodb:
host: localhost
port: 27017
database: plop
# uri : mongodb: //localhost:27017/plop
3. MongoTemplate 사용
3.1. MongoRepository 사용 한계
MongoRepository는 JpaRepository처럼 메소드명을 통해 쿼리를 구성할 수 있습니다.
CrudRepository를 상속하기 때문에 기본적인 CRUD(save, findById...)를 메소드로 질의할 수 있습니다.
MongoDB 를 사용하면서 여러 데이터를 넣어보고 조회해보고 하다보니깐 더 복잡한 쿼리가 필요했고
MongoRepository 만으로는 몽고DB를 활용하기에 한계가 있었습니다.
MongoRepository 예시)
public interface RoomRepository extends MongoRepository<RoomCollection,String>{
Optional<RoomCollection> findByRoomId(String roomId);
}
3.2. MongoTemplate 사용
그래서 DB 접근 방법으로 MongoTemplate과 QueryDSL를 쓰는 방법이 있었습니다.
QueryDSL도 충분히 좋지만 MongoTemplate으로도 충분히 해결할 수 있었고 사용해보니깐 좀더 재미있어서
MongoTemplate을 사용하기로 하였습니다.
3.3. MongoRepository와 MongoTemplate 함께 사용하기
[MongoDB] MongoTemplate과 MongoRepository를 함께 사용할 순 없는 걸까? (velog.io)
MongoRepository의 메소드 명명 규칙으로 최소한의 CRUD를 사용할 수 있고,
복잡한 구문의 경우 MongoTemplate를 사용하여 구현할 수 있습니다.
Service 단에서 RoomRepository를 사용하고 있었고 확장성을 고려해 다형성을 통해 RoomRepository를 그대로 가져가고 싶었기에 MongoTemplate를 사용하는 구현 클래스를 만들어 사용하기로 하였습니다.
4. 채팅 메시지 서비스
4.1. 채팅 메시지
MessageCollection
@Getter
@NoArgsConstructor
//@QueryEntity
@Document(collection = "messages")
public class MessageCollection {
@Id
private String _id;
private String roomId;
private MessageType type;
private String senderId;
private String content;
private LocalDateTime createdAt;
@Builder
public MessageCollection(MessageType type, String roomId, String senderId, String content, LocalDateTime createdAt) {
this.type = type;
this.roomId = roomId;
this.senderId = senderId;
this.content = content;
this.createdAt = createdAt;
}
}
@Document
jpa의 Entity 처럼 몽고db에서 영속되게 할 수 있는 도매인 객체 식별
LocalDateTime createdAt;
이전에는 Date 타입이였는데 Java8+ 에서는 Date 클래스의 단점을 보완한 LocalDateTime 도입으로 사용을 지양
[java] Date, LocalDateTime 사용법 간단 정리 (tistory.com)
ChatMessageService
@Slf4j
@Service
public class ChatMessageService {
private final ChatMessageRepository chatMessageRepository;
private static final int SIZE = 50;
public ChatMessageService(ChatMessageRepository chatMessageRepository) {
this.chatMessageRepository = chatMessageRepository;
}
public ChatMessageDto saveChatMessage(ChatMessageDto chatMessageDto){
MessageCollection messageCollection = chatMessageRepository.save(MessageCollection.builder()
.type(chatMessageDto.getMessage_type())
.roomId(chatMessageDto.getRoom_id())
.senderId(chatMessageDto.getSender_id())
.content(chatMessageDto.getContent())
.createdAt(LocalDateTime.now())
.build());
return convertEntityToDto(messageCollection);
}
public List<ChatMessageDto> getNewMessages(String roomId, String readMsgId){
List<MessageCollection> messageCollections = chatMessageRepository.getNewMessages(roomId, readMsgId);
return messageCollections.stream().map(m -> convertEntityToDto(m)).collect(Collectors.toList());
}
public List<ChatMessageDto> getAllMessagesAtRoom(String roomId) {
return chatMessageRepository.getAllMessagesAtRoom(roomId).stream().map(mc -> convertEntityToDto(mc)).collect(Collectors.toList());
}
public Page<ChatMessageDto> chatMessagePagination(String roomId, int page){
Page<MessageCollection> messageCollectionPage = chatMessageRepository.findByRoomIdWithPagingAndFiltering(roomId, page, SIZE);
Page<ChatMessageDto> chatMessageDtoPage = messageCollectionPage.map(new Function<MessageCollection, ChatMessageDto>() {
@Override
public ChatMessageDto apply(MessageCollection messageCollection) {
return convertEntityToDto(messageCollection);
}
});
return chatMessageDtoPage;
}
private ChatMessageDto convertEntityToDto(MessageCollection messageCollection) {
return ChatMessageDto.builder()
.room_id(messageCollection.getRoomId())
.message_type(messageCollection.getType())
.sender_id(messageCollection.getSenderId())
.content(messageCollection.getContent())
.created_at(messageCollection.getCreatedAt())
.message_id(messageCollection.get_id())
.build();
}
public void deleteChat(String id){
chatMessageRepository.deleteById(id);
}
}
지금 생각하니 페이지네이션 size를 설정파일에 적어두면 좋을 것 같다는 생각이 듭니다.
createDmRoom 메소드에서 null 체크부분에서 고민있었습니다.
null체크를 위해 (object == null) or ObjectUtils.isEmpty() 메소드를 수행하는 것에 대한 것입니다.
여러 글들을 읽어보고 내린 결론은 "== null" 이 더 직관적이라는 것입니다.
.isEmpty는 비어있는지 확인하는 것 같지만 내부적으로 null체크도 수행하기 때문에 애매한 부분이 있다고 합니다.
ChatMessageRepository
public interface ChatMessageRepository extends MongoRepository<MessageCollection,String>, ChatMongoTemplateRepository {
}
ChatMongoTemplateRepository
public interface ChatMongoTemplateRepository {
MessageCollection getLastMessage(String roomId);
List<MessageCollection> getNewMessages(String roomId, String readMsgId);
List<MessageCollection> getAllMessagesAtRoom(String roomId);
Page<MessageCollection> findByRoomIdWithPagingAndFiltering(String roomId, int page, int size);
}
ChatMessageRepositoryImpl
MongoTemplate을 사용한 구현체인데요. 여기서부터는 자세히 보도록 하겠습니다.
public class ChatMessageRepositoryImpl implements ChatMongoTemplateRepository{
private final MongoTemplate mongoTemplate;
public ChatMessageRepositoryImpl(MongoTemplate mongoTemplate) {
this.mongoTemplate = mongoTemplate;
}
@Override
public MessageCollection getLastMessage(String roomId) {
// List<MessageCollection> messageCollectionList = mongoTemplate.find(query
// .with(Sort.by(Sort.Direction.DESC,"createdAt")).limit(1), MessageCollection.class);
// if(messageCollectionList.size() == 0){
// messageCollectionList.add(new MessageCollection());
// }
// return messageCollectionList.get(0);
Query query = Query.query(Criteria.where("roomId").is(roomId)).with(Sort.by(Sort.Direction.DESC,"createdAt"));
query.fields().exclude("roomId");
query.fields().exclude("type");
MessageCollection messageCollection = mongoTemplate.findOne(query, MessageCollection.class);
if(messageCollection == null){
return new MessageCollection();
}
return messageCollection;
}
@Override
public List<MessageCollection> getNewMessages(String roomId, String readMsgId) {
ObjectId mObjId = new ObjectId(readMsgId);
LocalDateTime createAt = mongoTemplate.findOne(Query.query(Criteria.where("_id").is(mObjId)), MessageCollection.class).getCreatedAt();
List<MessageCollection> messageCollectionList = mongoTemplate.find(
Query.query(Criteria.where("roomId").is(roomId))
.addCriteria(Criteria.where("createdAt").gt(createAt)).with(
Sort.by(Sort.Direction.DESC,"createdAt"))
, MessageCollection.class);
return messageCollectionList;
}
@Override
public List<MessageCollection> getAllMessagesAtRoom(String roomId) {
Query query = Query.query(Criteria.where("roomId").is(roomId)).with(
Sort.by(Sort.Direction.DESC,"createdAt"));
return mongoTemplate.find(query,MessageCollection.class);
}
@Override
public Page<MessageCollection> findByRoomIdWithPagingAndFiltering(String roomId, int page, int size) {
Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
Query query = new Query()
.with(pageable)
.skip(pageable.getPageSize() * pageable.getPageNumber()) // offset : ~5, ~10
.limit(pageable.getPageSize());
query.addCriteria(Criteria.where("roomId").is(roomId));
List<MessageCollection> messageCollections = mongoTemplate.find(query, MessageCollection.class);
Page<MessageCollection> messageCollectionPage = PageableExecutionUtils.getPage(
messageCollections,
pageable,
()-> mongoTemplate.count(query.skip(-1).limit(-1), MessageCollection.class) // 정확한 도큐먼트 갯수 구하기 위함
);
return messageCollectionPage;
}
}
▶ 마지막 최신 메시지
@Override
public MessageCollection getLastMessage(String roomId) {
Query query = Query.query(Criteria.where("roomId").is(roomId)).with(Sort.by(Sort.Direction.DESC,"createdAt"));
query.fields().exclude("roomId");
query.fields().exclude("type");
MessageCollection messageCollection = mongoTemplate.findOne(query, MessageCollection.class);
if(messageCollection == null){
return new MessageCollection();
}
return messageCollection;
}
역순 정렬을 해서 가장 최신 값 하나를 조회하는 쿼리입니다.
필요없는 데이터는 exclude를 사용해여 제외시켰습니다.
▶ 채팅 메시지 저장
chatMessageRepository.save(messageCollection)
MongoRepository를 사용해 데이터 저장
몽고디비에 데이터 저장 완료
▶ 채팅방 새 메시지 받기
채팅 생성일을 비교하여 내가 마지막으로 읽었던 채팅(readMsgId)보다 최근에 생긴 채팅들을 받기
@Override
public List<MessageCollection> getNewMessages(String roomId, String readMsgId) {
ObjectId mObjId = new ObjectId(readMsgId);
LocalDateTime createAt = mongoTemplate.findOne(Query.query(Criteria.where("_id").is(mObjId)), MessageCollection.class).getCreatedAt();
List<MessageCollection> messageCollectionList = mongoTemplate.find(
Query.query(Criteria.where("roomId").is(roomId))
.addCriteria(Criteria.where("createdAt").gt(createAt)).with(
Sort.by(Sort.Direction.DESC,"createdAt"))
, MessageCollection.class);
return messageCollectionList;
}
고민을 많이 했었던 기능입니다.
- 우선 내가 최근에 읽었던 메시지id의 createdAt 정보를 가져옵니다.
- 다음 createdAt 보다 큰 값($gt) 즉, 최신 메시지들의 정보를 내림차순하여 가져오는 쿼리입니다.
mongoTemplate 을 작성하기전 몽고db 쿼리를 먼저 생각하고 작성했습니다.
// 몽고db 쿼리문
db.messages.findOne(ObjectId('63d9ecfce6304b424a983a57'))["createAt"]
// MongoTemplate 쿼리문
ObjectId mObjId = new ObjectId(readMsgId);
LocalDateTime createAt = mongoTemplate.findOne(Query.query(Criteria.where("_id").is(mObjId))
해당 createAt 변수를 통해 나머지 쿼리 작성
Criteria.where("roomId").is(roomId) : roomId(채팅방 id) 와 일치
Criteria.where("createdAt").gt(createAt) : 생성일자보다 큰 조건
Sort.by(Sort.Direction.DESC,"createdAt") : "createdAt" 기준으로 내림차순(DESC), with 메소드로 파이프라인 생성
▶ 해당 채팅방내 전체 메시지 가져오기
@Override
public List<MessageCollection> getAllMessagesAtRoom(String roomId) {
Query query = Query.query(Criteria.where("roomId").is(roomId)).with(
Sort.by(Sort.Direction.DESC,"createdAt"));
return mongoTemplate.find(query,MessageCollection.class);
}
roomId(채팅방 id) 와 일치, "createdAt" 기준으로 내림차순(DESC)
<몽고db 쿼리>
db.messages.find({roomId : '71b5354c-6b6c-4cc4-aac9-fea92f3af891'}).sort({createdAt:-1})
▶ 내림차순으로 해당 채팅방 Pagination, 사이즈 N = 50 고정
@Override
public Page<MessageCollection> findByRoomIdWithPagingAndFiltering(String roomId, int page, int size) {
Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
Query query = new Query()
.with(pageable)
.skip(pageable.getPageSize() * pageable.getPageNumber()) // offset : ~5, ~10
.limit(pageable.getPageSize());
query.addCriteria(Criteria.where("roomId").is(roomId));
List<MessageCollection> messageCollections = mongoTemplate.find(query, MessageCollection.class);
Page<MessageCollection> messageCollectionPage = PageableExecutionUtils.getPage(
messageCollections,
pageable,
()-> mongoTemplate.count(query.skip(-1).limit(-1), MessageCollection.class) // 정확한 도큐먼트 갯수 구하기 위함
);
return messageCollectionPage;
}
페이지네이션은 구현은 다른 글들을 참고하였고 Page와 Pageable을 통해 구성할 수 있습니다.
기존코드에서 내림차순으로 수정하였습니다.
Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
4.2. 채팅방
RoomCollection
@Getter
@ToString
//@QueryEntity
@Document(collection = "rooms")
public class RoomCollection {
@Id
private String _id;
private String roomId;
private String title;
private RoomType type;
private List<Member> members;
private List<String> managers;
private LocalDateTime createdAt;
@Builder
public RoomCollection(String roomId, String title, RoomType type, List<Member> members, List<String> managers) {
this.roomId = roomId;
this.title = title;
this.type = type;
this.members = members;
this.managers = managers;
this.createdAt = LocalDateTime.now();
}
public String makeTempRoomId(){
return RoomIdCreator.createRoomId();
}
public void setMembers(List<Member> members) {
this.members = members;
}
}
RoomRepository
@Repository
public interface RoomRepository extends MongoRepository<RoomCollection,String> , RoomMongoTemplateRepository{
Optional<RoomCollection> findByRoomId(String roomId);
boolean existsByRoomId(String roomId);
}
RoomMongoTemplateRepository
public interface RoomMongoTemplateRepository {
UpdateResult inviteMembers(ReqInviteDto reqInviteDto);
List<RoomCollection> findMyRoomsByUserId(String userId);
UpdateResult outOfTheRoom(String roomId, String userId);
RoomCollection matchDmMembers(ReqDmDto reqDmDto);
UpdateResult updateLastReadMsgId(ReqReadMessage reqReadMessage);
}
RoomRepositoryImpl
public class RoomRepositoryImpl implements RoomMongoTemplateRepository{
private final MongoTemplate mongoTemplate;
public RoomRepositoryImpl(MongoTemplate mongoTemplate) {
this.mongoTemplate = mongoTemplate;
}
@Override
public UpdateResult inviteMembers(ReqInviteDto reqInviteDto) {
RoomCollection rc = mongoTemplate.findOne(Query.query(Criteria.where("roomId").is(reqInviteDto.getRoom_id())), RoomCollection.class);
if(rc == null){
throw new CustomAPIException(ErrorCode.ROOM_NOT_FOUND_ERROR, "존재하지않는 채팅방-"+reqInviteDto.getRoom_id());
}
Query query = Query.query(Criteria.where("roomId").is(reqInviteDto.getRoom_id()));
Update update = new Update().push("members").each(convertToMembersList(reqInviteDto.getMembers()));
update.set("title",rc.getTitle()+","+String.join(",",reqInviteDto.getMembers()));
return mongoTemplate.updateFirst(query, update, RoomCollection.class);
}
@Override
public List<RoomCollection> findMyRoomsByUserId(String userId) {
Query query = Query.query(Criteria.where("members").elemMatch(
Criteria.where("userId").is(userId)
));
query.fields().exclude("_id");
query.fields().exclude("managers");
return mongoTemplate.find(query,RoomCollection.class);
}
@Override
public UpdateResult outOfTheRoom(String roomId, String userId) {
Query query = Query.query(Criteria.where("roomId").is(roomId));
Update update = new Update();
update.pull("members", new Query(Criteria.where("userId").in(userId)) );
return mongoTemplate.updateFirst(query,update,"rooms");
}
@Override
public RoomCollection matchDmMembers(ReqDmDto reqDmDto) {
Criteria criteria = new Criteria();
criteria.andOperator(Criteria.where("type").is(RoomType.DM),
Criteria.where("members").elemMatch(Criteria.where("userId").is(reqDmDto.getCreator())),
Criteria.where("members").elemMatch(Criteria.where("userId").is(reqDmDto.getMessage_to())));
return mongoTemplate.findOne(new Query(criteria),RoomCollection.class);
}
@Override
public UpdateResult updateLastReadMsgId(ReqReadMessage reqReadMessage) {
Query query = Query.query(Criteria.where("roomId").is(reqReadMessage.getRoom_id())
.andOperator(Criteria.where("members").elemMatch(Criteria.where("userId").is(reqReadMessage.getUser_id())))
);
Update update = new Update().set("members.$.lastReadMsgId",reqReadMessage.getMessage_id());
return mongoTemplate.updateFirst(query,update,"rooms");
}
public List<Member> convertToMembersList(List<String>list){
List<Member> members = new ArrayList<>();
list.forEach(s -> members.add(new Member(s, LocalDateTime.now())));
return members;
}
}
mongoTemplate를 사용하여 Query, Criteria와 함께 복잡한 쿼리 사용
고심했던 부분
▶ 마지막 읽었던 채팅메시지id 업데이트 : updateLastReadMsgId()
'lastReadMsgId' field in members Array
lastReadMsgId 이 부분을 업데이트하는 것인데요
어떻게 배열 안 필드값을 수정할 수 있을까요?
Google it 을 하며 MongoDB Shell을 통해 여러 테스트를 진행
그렇게 찾은 방법은 roomId 가 일치하고, members.userId 지정
members.$.lastReadMsgId : "63e45c835fdddc4755cb7292"
$ 표시를 통해 업데이트 할 부분 명확히 지정하여 쿼리 작성!
MongoDB 쿼리 작성 완료!
이제 이 쿼리를 mongoTemplate 으로 작성하면 된다!
Query query = Query.query(Criteria.where("roomId").is(reqReadMessage.getRoom_id()) // roomId 조건 지정
.andOperator(Criteria.where("members").elemMatch(Criteria.where("userId").is(reqReadMessage.getUser_id()))) // members array 내 userId 매치되는 조건
);
Update update = new Update().set("members.$.lastReadMsgId",reqReadMessage.getMessage_id()); // 조건에 부합하는 요소에 lastReadMsgId 값 set
return mongoTemplate.updateFirst(query,update,"rooms");
▶ 멤버 초대 : inviteMembers()
members라는 Object 배열에 속성 값을 삽입하는 작업입니다.
@Override
public UpdateResult inviteMembers(ReqInviteDto reqInviteDto) {
RoomCollection rc = mongoTemplate.findOne(Query.query(Criteria.where("roomId").is(reqInviteDto.getRoom_id())), RoomCollection.class);
if(rc == null){
throw new CustomAPIException(ErrorCode.ROOM_NOT_FOUND_ERROR, "존재하지않는 채팅방-"+reqInviteDto.getRoom_id());
}
Query query = Query.query(Criteria.where("roomId").is(reqInviteDto.getRoom_id()));
Update update = new Update().push("members").each(convertToMembersList(reqInviteDto.getMembers()));
update.set("title",rc.getTitle()+","+String.join(",",reqInviteDto.getMembers()));
return mongoTemplate.updateFirst(query, update, RoomCollection.class);
}
1. 우선 roomid 와 일치하는 Document를 특정짓습니다.
2. 그런 다음 메소드 파이프라인으로 멤버 object를 삽입합니다. : new Update().push("members").each()
3. title 값을 새로 설정합니다. update.set("title",rc.getTitle()+","+String.join(",",reqInviteDto.getMembers()))
▶ 자신이 속한 채팅방 조회
객체 배열 내의 속성 값이 일치하는 Document를 찾습니다.
만약 자신의 userId 가 "유저" 일 경우
해당 조건에 부합하는 모든 Docuements를 조회합니다.
@Override
public List<RoomCollection> findMyRoomsByUserId(String userId) {
Query query = Query.query(Criteria.where("members").elemMatch(
Criteria.where("userId").is(userId)
));
query.fields().exclude("_id");
query.fields().exclude("managers");
return mongoTemplate.find(query,RoomCollection.class);
}
1. members 라는 객체 리스트 내에 userId가 일치하는 Document 조회
2. 사용하지 않는 변수는 exclude 메서드로 제외
▶ 채팅방 나가기
멤버 객체 리스트에서 하나의 객체를 제거시키는 쿼리입니다.
@Override
public UpdateResult outOfTheRoom(String roomId, String userId) {
Query query = Query.query(Criteria.where("roomId").is(roomId));
Update update = new Update();
update.pull("members", new Query(Criteria.where("userId").in(userId)) );
return mongoTemplate.updateFirst(query,update,"rooms");
}
update.pull() 메소드를 사용하여 제거합니다.
▶ 이미 개설된 1:1 채팅방 있는지 조회
@Override
public RoomCollection matchDmMembers(ReqDmDto reqDmDto) {
Criteria criteria = new Criteria();
criteria.andOperator(Criteria.where("type").is(RoomType.DM),
Criteria.where("members").elemMatch(Criteria.where("userId").is(reqDmDto.getCreator())),
Criteria.where("members").elemMatch(Criteria.where("userId").is(reqDmDto.getMessage_to())));
return mongoTemplate.findOne(new Query(criteria),RoomCollection.class);
}
1:1을 나타내는 type이 DM인 채팅방 중에
나와 상대방이 1:1채팅방을 개설적 있는지 조회합니다.
컨트롤러 작성
@Slf4j
@RestController
@RequestMapping("/chatting/room")
@RequiredArgsConstructor
public class ChatRoomController {
private final ChatRoomService chatRoomMongoService;
private String getTokenToUserId(String jwt){
return jwtTokenProvider.getUserInfo(jwtTokenProvider.removeBearer(jwt)).getUserId();
}
@PostMapping("/v1/dm-creation")
public ResponseEntity<RespRoomDto> dmCreation(@RequestHeader("Authorization") String jwt, @RequestBody ReqDmDto reqDmDto){
String userId = getTokenToUserId(jwt);
reqDmDto.setCreator(userId);
return new ResponseEntity<>(chatRoomMongoService.createDmRoom(reqDmDto),HttpStatus.CREATED);
}
...
}
ReqDmDto
@Setter
@Getter
public class ReqDmDto {
private String creator;
private String message_to;
}
RespRoomDto
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class RespRoomDto {
private String room_id;
private String title;
private RoomType type;
private List<Member> members;
private List<String> managers;
private LocalDateTime createdAt;
}
이렇게 해서 MongoDB에 MongoTemplate를 사용하여 접근하고 쿼리문을 작성해보았습니다.
MongoTemplate을 통해 조금 더 복잡한 쿼리와 페이지네이션도 쉽게 할 수 있어서
NoSQL을 쓴다면 Redis와 함께 사용해보기 좋은 것 같습니다.
또한 Mongo Atlas라는 클라우드 환경에서 MongoDB를 운용할 수 있어서 같이 사용해봐도 좋다고 생각합니다.
Mongo Atlas 화면입니다. 무료 버전이지만 클러스터를 통해 샤딩도 되어있고
Atlas에서 AWS 서울 리전에 배포를 해줍니다.
총 용량은 512MB 까지 제공해주는 걸로 아는데 1만개 데이터를 넣어도 2.27MB밖에 차지하지 않아 충분히 초기 개발이나 미니 프로젝트에 활용할 수 있을 것 같습니다.
감사합니다.
'Dev > [프로젝트] 2022 Winter Dev Camp' 카테고리의 다른 글
2022 스마일게이트 윈터데브캠프를 통해 성장하기 7 - 성능 측정, 성능 향상을 위한 리팩토링, 수정과 보완 (0) | 2023.03.05 |
---|---|
2022 스마일게이트 윈터데브캠프를 통해 성장하기 6 - 프로젝트 배포 (0) | 2023.03.04 |
2022 스마일게이트 윈터데브캠프를 통해 성장하기 4 - 구현 : 접속상태 서비스 (0) | 2023.03.01 |
2022 스마일게이트 윈터데브캠프를 통해 성장하기 3 - 구현 : 채팅 서비스 (0) | 2023.02.28 |
2022 스마일게이트 윈터데브캠프를 통해 성장하기 2 - 프로젝트 설계와 마음가짐 (0) | 2023.02.26 |
댓글