
React 경험이 거의 없는 개발자가 Cursor와 함께 React, Node, 알라딘 Open API를 사용한 서비스를 7일 동안 개발한 이야기입니다.
저는 보통 책을 중고로 삽니다. 종종 여러 권을 한 번에 살 때도 있습니다. 이럴 때 아쉬운 점은 배송비입니다. 새 책은 일정 금액이 넘으면 보통 무료배송이니까요. 중고는 무료배송이 잘 없는 데다 판매자마다 배송비가 따로 붙어서 왠지 더 억울합니다.
한 번은 1, 2, 3권 시리즈 책을 사려고 했습니다. 배송비가 따로 붙는 게 싫어서 세 권을 모두 갖고 있는 판매자를 찾았습니다.

묶음배송 조합을 찾는 데는 성공했지만, 문제가 있었습니다. 그 판매자의 매물이 최저가가 아니었습니다. 차라리 서로 다른 판매자의 최저가 매물을 각각 사는 게 더 싼 겁니다. 결국 1, 2, 3권의 페이지를 각각 열어놓고, 판매자가 같으면서도 저렴한 매물을 찾다가 눈이 빠질 뻔 한 경험이 있습니다. 그렇게 이 서비스를 기획하게 되었습니다.
https://aladin-used-book-optimizer.com




Cursor의 도움이 컸습니다. 저는 React 경험이 거의 없기 때문입니다. 일단 무작정 기획안을 넣어봤습니다. 내용은 대충 다음과 같았습니다.
기획
중고 책을 한 번에 여러 권 구매하고자 할 때, 책마다 배송비가 따로 붙어서 오히려 새 책을 사는 것이나 별 다를 바 없는 가격이 되는 경우가 있다. 만약에 내가 중고로 사고자 하는 책이 같은 셀러로부터 합리적인 가격에 제공되고 있는 것을 찾을 수 있다면 배송비를 아낄 수 있을 것이다.
내가 중고로 사고자 하는 책을 리스트업한다. 각 책의 최저가만 골라서 사는 방법, 내가 사고자 하는 책 중 두 권 이상의 책을 판매하고 있는 셀러에게 한꺼번에 사는 방법 등 여러 가지 방법을 고려하여 가장 싸게 살 수 있는 조합을 알려준다.
형태
- 웹사이트: React + typescript + 백엔드? 어떻게 구성할 지 모르겠어
- 알라딘 Open API 사용. 아래 링크 참고해줘
유저 플로우
구매하고자 하는 중고책 리스트 만들기
- 검색 API를 사용하여 중고 책을 검색 → 검색 결과 노출
- 검색 결과 중 내가 사려는 중고 책을 선택
- 중고 책 리스트에 선택한 중고 책이 등록됨
- 1.-3.를 반복
가장 싸게 살 수 있는 조합 찾기
- ‘최저가 조합 찾기’ 버튼 클릭
- 책마다 각각 가격 + 배송비 합이 가장 저렴한 매물로 조합하기
- ‘2.’의 책값 + 배송비 총합보다 저렴한 모든 매물 조합 찾기
- ‘3.’의 리스트 중 셀러가 같은 곳이 있다면 배송비 제외하기
- ‘4.’의 리스트 중 ‘2.’보다 싼 곳이 있는지 비교
Cursor는 생각보다 저돌적입니다. 진행할까요? 묻지 않고 그냥 해버립니다. 프롬프트를 넣으니 1분도 안 걸려서 웹사이트를 뚝딱 완성합니다.
돌려보니 에러가 있었습니다. localhost:5173에 접속해도 빈 화면만 나왔습니다. 일단 에러 메세지를 던져줬더니 알아서 문제 해결을 위해 필요한 태스크를 정의하고 수행합니다. 마지막으로는 제대로 수정이 되었는지 스스로 테스트까지 합니다.

머지 않아 다음과 같은 화면이 나타났습니다.

제가 첨부한 링크를 읽었는지, 이미 알라딘 책 정보를 가져오는 것까지 거의 정확하게 구현이 돼 있었습니다.
/* aladinApiService.ts */
import dotenv from 'dotenv';
import axios from 'axios';
import { Book } from '../types';
// Load environment variables
dotenv.config();
const ALADIN_API_URL = process.env.ALADIN_API_URL || 'https://www.aladin.co.kr/ttb/api';
const ALADIN_API_KEY = process.env.ALADIN_API_KEY;
async searchBooks(query: string, page: number = 1, maxResults: number = 20): Promise<Book[]> {
const response = await axios.get(`${ALADIN_API_URL}/ItemSearch.aspx`, {
params: {
ttbkey: this.apiKey,
Query: query,
QueryType: 'Title',
MaxResults: maxResults,
start: (page - 1) * maxResults + 1,
SearchTarget: 'Book',
output: 'js',
Version: '20131101'
}
});
const data = response.data;
const books = data.item.map((item: any) => ({
isbn: item.isbn,
title: item.title,
author: item.author,
publisher: item.publisher,
pubDate: item.pubDate,
cover: item.cover,
price: parseInt(item.priceStandard),
discount: parseInt(item.priceSales),
description: item.description,
link: item.link
}));
// Error 처리
return books;
}
async getBookDetails(isbn: string): Promise<any> {
const response = await axios.get(`${ALADIN_API_URL}/ItemLookUp.aspx`, {
params: {
ttbkey: this.apiKey,
itemIdType: 'ISBN',
ItemId: isbn,
output: 'js',
Version: '20131101',
OptResult: 'usedList'
}
});
// Error 처리
return response.data;
}
제가 할 일이라고는 알라딘 Open API에 로그인하여 key를 발급하고, 이를 환경변수에 적용시키는 것뿐이었습니다. 아직 URL이 없어서 일단 제 블로그 주소를 넣어주었습니다. '추가'를 누르면 바로 Open API 인증키가 생성됩니다.

실제 알라딘 API 키를 넣어주고 나니 책 검색이 제대로 되기 시작합니다.

실제 알라딘 검색 결과는 다음과 같았습니다.

차이가 조금 있는데, API 호출 기본값이 국내 도서만 가져오게 되어있어서 그렇습니다. API 호출 시 'SearchTarget' 파라미터를 설정하여 바꿀 수 있습니다.

책을 위시리스트에 등록하면 해당 책의 중고 매물 리스트를 가져오게 하고 싶었습니다. 문제는 알라딘 상품 조회 API 응답에는 해당책의 개별 중고 매물 정보가 없다는 것입니다.

대신 알라딘 직접배송, 이 광활한 우주점, 개인 셀러 각각의 중고 매물의 수와 페이지 링크를 제공합니다. 매물 리스트 페이지를 puppeteer, cheerio로 크롤링하여 중고 매물을 가져오게 했습니다.
// 알라딘 중고책 크롤링 핵심 코드
import puppeteer from 'puppeteer';
import * as cheerio from 'cheerio';
interface UsedBook {
price: number;
shippingCost: number;
seller: string;
condition: string;
originalPrice: number;
}
async function crawlUsedBooks(url: string): Promise<UsedBook[]> {
// 1. 브라우저 실행
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox']
});
try {
const page = await browser.newPage();
// 2. User-Agent 설정 (봇 차단 방지)
await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36');
// 3. 페이지 로드 및 대기
await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });
await page.waitForNetworkIdle({ timeout: 3000, idleTime: 1000 });
// 4. HTML 내용 가져오기
const content = await page.content();
const $ = cheerio.load(content);
const usedBooks: UsedBook[] = [];
// 5. 중고책 테이블 파싱
$('.Ere_usedsell_table tbody tr').each((index: number, element: cheerio.Element) => {
const $row = $(element);
// 헤더 행 건너뛰기
if ($row.find('.sell_tableTF').length > 0) return;
// 가격 추출
const priceElement = $row.find('.price .Ere_sub_pink span.Ere_fs20').first();
const priceText = priceElement.text().replace(/[^0-9]/g, '');
const price = parseInt(priceText) || 0;
// 배송비 추출
const shippingLi = $row.find('.price li').filter(function() {
return $(this).text().includes('배송비');
}).first();
const shippingCost = shippingLi.length > 0
? parseInt(shippingLi.text().replace(/[^0-9]/g, '')) || 2500
: 2500;
// 판매자 정보
const seller = $row.find('.seller a').first().text().trim() || `판매자${index + 1}`;
// 상태 정보
const condition = $row.find('.Ere_sub_top span').first().text().trim() || '중';
if (price > 0) {
usedBooks.push({
price: price + shippingCost,
shippingCost: shippingCost,
seller: seller,
condition: condition,
originalPrice: price
});
}
});
return usedBooks.sort((a, b) => a.price - b.price);
} finally {
await browser.close();
}
}
서비스의 핵심이 되는 로직입니다. 이 부분에서 아직 AI가 대체하기 어려운 부분이 있음을 느꼈습니다. 오류가 꽤 많아서 직접 짜야 했습니다.
- 각 책의 모든 중고책들의 조합을 만든다. 예를 들어 4가지 책이 있고, 각 책의 중고책 수가 3권, 5권, 7권, 10권 이라면 모든 조합의 수는 357*10가지가 된다.
- 각 조합마다 다음을 수행한다.
a. 조합을 구성하는 책들의 책값을 먼저 더해 책값의 총합을 구한다.
b. 조합을 구성하는 책들의 배송비를 하나씩 더한다. 이때, 먼저 배송비를 더한 책 중 책 중 같은 셀러가 판매하고 있는 책이 있다면 배송비를 더하지 않는다. 즉, 같은 셀러가 판매하는 책들 모두에 대하여 배송비는 한 번만 적용한다.
c. 배송비까지 모두 더하여 총 가격을 산출한다.- 2.번을 수행할 때마다 총 가격을 비교하여 최저가 조합을 갱신한다.
/**
* 중고책 구매 최적화 메인 함수
*
* @param wishlist - 최적화할 책 목록
* @returns 최적 조합과 총 비용
*/
function optimizeBookPurchase(wishlist: WishlistItem[]): {
totalCost: number;
combinations: UsedBook[];
stats: { totalCombinations: number; evaluatedCombinations: number; pruningRate: string };
} {
// 1. 단순 최저가 조합 계산 (첫 번째 책 선택)
const simpleMinBooks = wishlist.map(item => item.usedBooks[0]);
// 단순 최저가 조합의 배송비 계산 (같은 셀러는 한 번만)
const sellerShippingCosts = new Map<string, number>();
simpleMinBooks.forEach(book => {
if (!sellerShippingCosts.has(book.seller)) {
sellerShippingCosts.set(book.seller, book.shippingCost);
} else {
const currentShipping = sellerShippingCosts.get(book.seller)!;
if (book.shippingCost > currentShipping) {
sellerShippingCosts.set(book.seller, book.shippingCost);
}
}
});
const simpleMinShippingCost = Array.from(sellerShippingCosts.values())
.reduce((sum, cost) => sum + cost, 0);
console.log(`💰 단순 최저가 조합 총 배송비: ${simpleMinShippingCost}원`);
// 2. 브루트포스로 최적 조합 탐색
let bestCombination: UsedBook[] = [];
let bestTotalCost = Infinity;
let evaluatedCombinations = 0;
// 총 조합 수 계산
const totalCombinations = wishlist.reduce((total, item) => total * item.usedBooks.length, 1);
console.log(`🔢 총 가능한 조합 수: ${totalCombinations.toLocaleString()}개`);
// 재귀 함수로 모든 조합 탐색
function findOptimalCombination(bookIndex: number, currentCombination: number[]): void {
// 모든 책을 선택했으면 조합 평가
if (bookIndex >= wishlist.length) {
evaluatedCombinations++;
// 조합에 해당하는 중고책들 선택
const selectedBooks: UsedBook[] = [];
for (let i = 0; i < currentCombination.length; i++) {
selectedBooks.push(wishlist[i].usedBooks[currentCombination[i]]);
}
// 총 비용 계산 (책값 + 배송비)
const totalBookCost = selectedBooks.reduce((sum, book) => sum + book.price, 0);
// 배송비 계산 (같은 셀러는 한 번만)
const sellerShippingCosts = new Map<string, number>();
selectedBooks.forEach(book => {
if (!sellerShippingCosts.has(book.seller)) {
sellerShippingCosts.set(book.seller, book.shippingCost);
} else {
const currentShipping = sellerShippingCosts.get(book.seller)!;
if (book.shippingCost > currentShipping) {
sellerShippingCosts.set(book.seller, book.shippingCost);
}
}
});
const totalShippingCost = Array.from(sellerShippingCosts.values())
.reduce((sum, cost) => sum + cost, 0);
const totalCost = totalBookCost + totalShippingCost;
// 최적해 업데이트
if (totalCost < bestTotalCost) {
bestTotalCost = totalCost;
bestCombination = [...selectedBooks];
}
return;
}
// 현재 책의 중고책들에 대해 반복
for (let i = 0; i < wishlist[bookIndex].usedBooks.length; i++) {
findOptimalCombination(bookIndex + 1, [...currentCombination, i]);
}
}
// 3. 최적화 실행
findOptimalCombination(0, []);
// 통계 계산
console.log(`📊 총 ${evaluatedCombinations.toLocaleString()}개 조합 평가 완료`);
return {
totalCost: bestTotalCost,
combinations: bestCombination,
stats: {
totalCombinations,
evaluatedCombinations,
pruningRate: '0.00'
}
};
}
구현 후 확인한 결과, 위시리스트의 책이 3-4권 정도까지는 거의 즉각 결과가 나오는데, 5권부터는 시간이 꽤 걸렸습니다. 알고리즘 개선이 필요해 보였습니다. 애초에 최적 조합을 벗어나는 케이스를 연산에서 제외시킴으로써 성능을 개선해보기로 했습니다.
변경되는 점
- 각 책별 중고책 리스트를 오름차순 정렬을 먼저 하여, 단순 최저가 조합을 찾을 때, 각 책의 중고책 리스트의 첫번째 책만 가져와 조합하면 되도록 함.
- 책값이 이미 배송비 절약을 무마할 정도로 비싸다면 연산에서 제외
성능 최적화를 위한 가지치기 알고리즘
- 위시리스트에 n가지 종류의 책이 있다 (0 < n)
- 각 책에는 m(n)개의 중고책이 있다. (0 < m(n))
- m(n)개의 중고책을 책 가격 기준 오름차순으로 정렬
- 단순 최저가 구하기: n가지 책의 책별 중고책 리스트의 첫번째 중고책(책값 가장 저렴)들의 조합의 총 가격(총 책값 + 총 배송비, 같은 셀러 있을 시 배송비 절약 적용)
- 단순 최저가 조합의 총 배송비 구하기
- n번째 책의 0번째 중고책의 책값과 i(0 <= i < m(n))번째의 중고책의 책값의 차이가 단순 최저가 조합의 총 배송비보다 크다면 단순 최저가 조합이 나으므로, iterate을 마친다.
이를 반영하여 알고리즘을 다음과 같이 개선했습니다.
function optimizeBookPurchase(wishlist: WishlistItem[]): {
totalCost: number;
combinations: UsedBook[];
stats: { totalCombinations: number; evaluatedCombinations: number; pruningRate: string };
} {
// 1. 각 책의 중고책을 가격 기준 오름차순 정렬
const sortedWishlist = wishlist.map(item => ({
...item,
usedBooks: [...item.usedBooks].sort((a, b) => a.price - b.price)
}));
// 2. 단순 최저가 조합 계산 (가지치기 기준용)
const simpleMinBooks = sortedWishlist.map(item => item.usedBooks[0]);
// 단순 최저가 조합의 배송비 계산 (같은 셀러는 한 번만)
const sellerShippingCosts = new Map<string, number>();
simpleMinBooks.forEach(book => {
if (!sellerShippingCosts.has(book.seller)) {
sellerShippingCosts.set(book.seller, book.shippingCost);
} else {
const currentShipping = sellerShippingCosts.get(book.seller)!;
if (book.shippingCost > currentShipping) {
sellerShippingCosts.set(book.seller, book.shippingCost);
}
}
});
const simpleMinShippingCost = Array.from(sellerShippingCosts.values())
.reduce((sum, cost) => sum + cost, 0);
console.log(`💰 단순 최저가 조합 총 배송비: ${simpleMinShippingCost}원 (가지치기 기준)`);
// 3. 브루트포스 + 가지치기로 최적 조합 탐색
let bestCombination: UsedBook[] = [];
let bestTotalCost = Infinity;
let evaluatedCombinations = 0;
// 총 조합 수 계산
const totalCombinations = sortedWishlist.reduce((total, item) => total * item.usedBooks.length, 1);
console.log(`🔢 총 가능한 조합 수: ${totalCombinations.toLocaleString()}개`);
// 재귀 함수로 모든 조합 탐색
function findOptimalCombination(bookIndex: number, currentCombination: number[]): void {
// 모든 책을 선택했으면 조합 평가
if (bookIndex >= sortedWishlist.length) {
evaluatedCombinations++;
// 조합에 해당하는 중고책들 선택
const selectedBooks: UsedBook[] = [];
for (let i = 0; i < currentCombination.length; i++) {
selectedBooks.push(sortedWishlist[i].usedBooks[currentCombination[i]]);
}
// 총 비용 계산 (책값 + 배송비)
const totalBookCost = selectedBooks.reduce((sum, book) => sum + book.price, 0);
// 배송비 계산 (같은 셀러는 한 번만)
const sellerShippingCosts = new Map<string, number>();
selectedBooks.forEach(book => {
if (!sellerShippingCosts.has(book.seller)) {
sellerShippingCosts.set(book.seller, book.shippingCost);
} else {
const currentShipping = sellerShippingCosts.get(book.seller)!;
if (book.shippingCost > currentShipping) {
sellerShippingCosts.set(book.seller, book.shippingCost);
}
}
});
const totalShippingCost = Array.from(sellerShippingCosts.values())
.reduce((sum, cost) => sum + cost, 0);
const totalCost = totalBookCost + totalShippingCost;
// 최적해 업데이트
if (totalCost < bestTotalCost) {
bestTotalCost = totalCost;
bestCombination = [...selectedBooks];
}
return;
}
// 현재 책의 중고책들에 대해 반복
for (let i = 0; i < sortedWishlist[bookIndex].usedBooks.length; i++) {
// 가지치기 조건: 가격 차이가 배송비 절약 효과보다 크면 건너뛰기
if (i > 0) {
const currentBook = sortedWishlist[bookIndex];
const priceDifference = currentBook.usedBooks[i].price - currentBook.usedBooks[0].price;
if (priceDifference > simpleMinShippingCost) {
break; // 더 이상 탐색할 필요 없음
}
}
findOptimalCombination(bookIndex + 1, [...currentCombination, i]);
}
}
// 최적화 실행
findOptimalCombination(0, []);
// 가지치기 통계 계산
const skippedCombinations = totalCombinations - evaluatedCombinations;
const pruningRate = totalCombinations > 0 ? (skippedCombinations / totalCombinations * 100).toFixed(2) : '0.00';
console.log(`✂️ 가지치기 통계: ${evaluatedCombinations.toLocaleString()}개 조합 평가, ${skippedCombinations.toLocaleString()}개 건너뛰기 (${pruningRate}%)`);
return {
totalCost: bestTotalCost,
combinations: bestCombination,
stats: {
totalCombinations,
evaluatedCombinations,
pruningRate
}
};
}
확인 결과, 경우에 따라 최대 20% 정도 연산량을 줄일 수 있었습니다. 아무래도 권수가 많아질수록, 즉 개별 책의 배송비 합이 커질수록 많이 비싼 책만 가지치기 될 것입니다. 이 방법은 추가 개선이 필요해 보입니다.
로컬에서 테스트할 때는 성공적이었습니다. 속도도 나쁘지 않게 나와주었습니다. GitHub Actions로 CI/CD, AWS EC2 t3.micro에 자동 배포까지 세팅 후에도, 처음 테스트할 때는 양호한 성능을 보여주었습니다. 하지만 얼마 지나지 않아 심각한 문제가 발생했습니다.
크롤링 속도가 갑자기 심각하게 느려졌습니다. 심지어는 EC2 서버가 뻗어서 계속 reboot을 해야했습니다.
처음에는 코드 문제인 줄 알았는데, 알고 보니 EC2의 크레딧 정책 때문이었습니다. t3.micro 인스턴스는 CPU 크레딧을 소모하는 방식이라, 지속적인 크롤링 작업으로 인해 크레딧이 부족해져 성능이 급격히 저하된 것이었습니다. 크롤링이 이렇게 리소스를 많이 사용하는 작업인지도 몰랐습니다.
EC2의 한계를 깨달은 후, 더 적합한 서비스로 이전하기로 했습니다:
| 구분 | EC2 | Vercel + Railway |
|---|---|---|
| 크롤링 요청 처리 | 고정 리소스로 처리 → 리소스 부족 시 서버 크래시 | 동적 리소스 할당 → 자동 확장으로 안정적 처리 |
| 동시 요청 대응 | 여러 크롤링 요청 시 리소스 경합 → 서버 다운 | 각 요청을 독립적으로 처리 → 서버 안정성 유지 |
| 장애 복구 | 서버 다운 시 수동 재시작 필요 | 자동으로 새 인스턴스 생성 |
동시에 Puppeteer의 무거움도 문제였습니다. 메모리 사용량이 많고, 서버리스 환경에서는 Cold Start 시간이 길어집니다. 그래서 Axios + Cheerio로 변경했습니다.
| 구분 | Puppeteer | Axios |
|---|---|---|
| 브라우저 | 실제 브라우저 실행 | HTTP 요청만 |
| JavaScript | 동적 콘텐츠 처리 가능 | 정적 HTML만 |
| 성능 | 느림 (브라우저 오버헤드) | 빠름 |
| 메모리 | 높음 | 낮음 |
| 복잡도 | 높음 | 낮음 |
알라딘의 중고책 페이지는 대부분 정적 HTML로 구성되어 있어서 Axios 방식으로도 충분하다고 봤습니다.
개발이 진행될수록 Cursor가 생성하는 코드가 복잡해지고, 수정사항이 많아지기 시작했습니다:
Cursor가 제안하는 모든 코드를 무조건 수락하지 않고, 반드시 검토가 필요하다는 생각이 들었습니다. 특히 최저가 조합 찾기 등 조금만 알고리즘이 복잡해져도 간간히 오류를 만들곤 했습니다. 간단한 UI 수정 정도는 큰 문제가 아닙니다. 복잡한 알고리즘을 온전히 Cursor에게 맡기면, 점점 유지보수 불가능한 코드가 될 것입니다.
Cursor는 말하면 '뚝딱' 해버립니다. 한 번에 변경된 파일이 수십개가 생기기도 합니다. 코드가 내 컨트롤을 벗어나기 시작합니다. 변경사항과 이유를 명확히 알 수 있는 단위로 작업하고 커밋하듯이, Cursor에게 일을 시킬 때도, 이해하고 컨트롤할 수 있는 단위로 작업을 시키고, 동시에 커밋을 남겨가면서 진행하는 게 좋습니다.
Cursor가 생성한 초기 코드는 보통 하나의 큰 컴포넌트에 모든 로직이 들어있었습니다. 이를 작은 컴포넌트로 나누고, 커스텀 훅으로 로직을 분리하는 작업을 매번 했습니다.
7일이라는 짧은 시간에 완전한 웹서비스를 만들 수 있었습니다. 웬만한 부분은 생각 없이 에러메세지만 복붙해도 해결이 가능했습니다.
하지만 어느 지점을 넘어서 서비스가 커지기 시작하면 Cursor가 일관성을 잃기 시작하는 지점이 오는 것을 느꼈습니다. 점점 성능이 올라가겠지만, 현재로서는 개발자 스스로의 코드에 대한 이해 없이 Cursor만으로 개발하는 것은 한계가 있어 보입니다.
기술적 깊이가 필요한 부분이나 성능 최적화는 여전히 개발자의 경험과 판단이 필요했습니다. 복잡한 최적화 알고리즘은 여전히 직접 구현해야 합니다. Puppeteer vs Cheerio, EC2 vs Railway 같은 기술 선택은 개발자의 경험이 필요합니다. 경험이 있었더라면 애초에 Puppeteer를 선택하지 않았을 것입니다.
보통 AI를 주니어급 개발자에 비유하지만, 이번에 개발하면서 종종 시니어를 데리고 개발하는 기분이 들기도 했습니다. 로드맵을 짜주고, Cursor가 짠 코드 중 이해 안 가는 부분에 대해 설명을 부탁하기도 했습니다.
처음으로 AI를 써서 서비스 하나를 완성해봤습니다. 전에는 AI가 주니어 개발자인 나의 자리를 대체하는 건 아닐까 생각했습니다. 지금은 Cursor 덕분에 내가 훨씬 빠르게 성장할 수 있겠다는 생각이 듭니다.