-
Notifications
You must be signed in to change notification settings - Fork 3
๐ง ์ฐ์ ์์ ํ๋ก ์์ฒญ ์ ์ดํ๊ธฐ
๋ถ์ผ | ์์ฑ์ | ์์ฑ์ผ |
---|---|---|
BE | ๊น๋ฏผ์ | 24๋ 12์ 03์ผ |
ํ์ฌ ๊ฐ๊ฒฉ ์์น, ํ๋ฝ๋ฅ ์์์ ๋ํ API๋ฅผ ์์ฒญํ์ฌ ๋ฐ์ดํฐ๋ฅผ ์์งํ๊ธฐ ์ํ ๊ธฐ๋ฅ์ ๋ง๋ค์๋ค. ํด๋น ๊ธฐ๋ฅ์์๋ ๋ค์์ ๊ณผ์ ์ผ๋ก ์ฃผ์ API์ ๋ฐ์ดํฐ๋ฅผ ์์ฒญํ๊ณ ์ฒ๋ฆฌํ๊ฒ ๋๋ค.
- ์ฅ์ด ์ด๋ฆฌ๋ ์๊ฐ์ 1๋ถ ๊ฐ๊ฒฉ์ผ๋ก ์์น, ํ๋ฝ๋ฅ ์์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ๋๋ค.
- ์ด 30๊ฐ์ ์ข ๋ชฉ ์ค ์์ 20๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅ
- ์์ 20๊ฐ์ ์ค์๊ฐ ๋ฐ์ดํฐ๋ฅผ ์์ฒญ(1์ด์ 10๊ฐ ์์ฒญ)
- ์ ๋ฌ ๋ฐ์ ๋ฐ์ดํฐ๋ฅผ ์์ง ํ ์ ์ฅ
์ด๋ ์ฃผ์ API๋ ๊ณ์ข 1๊ฐ ๋น 1์ด์ 20๊ฐ์ ์์ฒญ์ด ๊ฐ๋ฅํ๊ธฐ ๋๋ฌธ์ ์ ์ ํ ์กฐ์ ์ด ํ์ํ๋ค. ๊ทธ๋ก์ธํด 20๊ฐ์ ์ข ๋ชฉ์ ๋ํ ๋ฐ์ดํฐ๋ฅผ ํ๋ฒ์ ๋ฐ์ง ์๊ณ 1์ด์ 10๊ฐ์ฉ ๋๋์ด์ ์ ๋ฌํ์๊ณ ์คํจํ์ ๋ 2์ด ๋๊ธฐ ํ ์ต๋ 5๋ฒ ์งํํ๋๋ก ํ๋ค.
async getDecreaseRankStocks(count = 5) {
try {
if (count === 0) return;
const result = await this.getFluctuationRankApiStocks(false);
const liveResult = await this.getFluctuationRankApiLive(result);
await this.datasource.transaction(async (manager) => {
await this.saveFluctuationRankStocks(result, manager);
await this.saveLiveData(liveResult, manager);
this.logger.info('decrease rank stocks updated');
});
} catch (error) {
this.logger.warn(error);
await new Promise((resolve) =>
setTimeout(() => resolve(this.getDecreaseRankStocks(--count)), 2000),
);
}
}
์์ฑํ ์ฝ๋๋ฅผ ํตํด ์ด๋ ์ ๋ ์์ฒญ ์ ์ด๊ฐ ๋ ๊ฒ์ด๋ผ๊ณ ์๊ฐํ๋ค. ํ์ง๋ง ์์์ธ๋ก ์๊ฐ๋ณด๋ค ์์ฒญ ์ ์ด๊ฐ ์ ์ด๋ฃจ์ด์ง์ง ์์๋ค.
๋ฐ๋ผ์ ํ๋ฃจ๋์ ์ผ๋ง๋ ์์ฒญ์ด ์คํจ๋ฅผ ํ์ธํ๋ค.
๋๋๊ฒ๋ 1๋ง ๊ฐ ์ด์์ ์์ฒญ์ด ์คํจ๊ฐ ๋์๋ค. ์ด๋ ๊ฒ ์ ํ๋ ์์ฒญ์ ์ ์ ์ดํ์ง ๋ชปํ๋ค๋ฉด ๋ง์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๊ฒ ๋๋ค. ์๋ฌ๊ฐ ๊ณ์ ๋ฐ์ํ์ฌ ์์ฒญ์ ๋ณด๋ผ ์ ์๋ ๊ธฐํ๊ฐ ๋ชจ๋ ๋ ์๊ฐ ๊ฒฐ๊ตญ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ง ๋ชปํ๋ ๋ฌธ์ ๋ ๋ถํ์ํ ์์ฒญ์ด ๋์ด๋๋ ๋ฌธ์ ๊ทธ๋ฆฌ๊ณ ํ๋ฒ ์กฐ์ ์ ์คํจํ๋ฉด ๋ง์ ์์ฒญ์ด ์ฐ์์ ์ผ๋ก ์คํจํ ์ ์๋ ๊ฐ๋ฅ์ฑ ๋ฑ ๋ค์ํ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๊ฒ ๋๋ค. ๋ฐ๋ผ์ ์ด๋ฅผ ์ ์ ์ดํ ์ ์๋ ๊ตฌ์กฐ๋ฅผ ๋ง๋ค์ด์ผ๋๋ค.
์์ฒญ ์ ์ ์ด๋ฅผ ์คํจํ ์์ธ์ ํ์ฌ ์์ฒญ์ ์ ์ดํ๊ธฐ ์ํ ๋ก์ง์ด ์ค์ ๋ก๋ ์ ์ด๊ฐ ์ ์๋๊ธฐ ๋๋ฌธ์ด์๋ค. ๊ธฐ์กด์ ์งํํ ์์ฒญ ์ ์ด ๋ก์ง์ ํฌ๊ฒ 2๊ฐ์ง์๋ค. ์ฐ์ ์ฒซ ๋ฒ์งธ๋ ๋ก์ง์ ๋ค์๊ณผ ๊ฐ๋ค.
ํด๋น ๋ก์ง์ ์ค์ผ์ค๋ฌ๊ฐ ์ผ์ ์๊ฐ์ด ๋๋ฌํ๋ฉด api ์์ฒญ์ ์งํํ๋ ๋ก์ง์ผ๋ก ๋งค์ฐ ๊ฐ๋จํ๋ค. ๋ฐ๋ผ์ 1๋ถ ์ฃผ๊ธฐ๋ก ์ ๋ฐ์ดํธํ๋ ๋ฐ์ดํฐ(๋ณ๋๋ฅ ์์, ๊ทธ์ ์ฐ๊ด๋ ๋ผ์ด๋ธ ๋ฐ์ดํฐ ๋ฑ)์ ์ฐ์ด๊ฒ ๋๋ค. ํน์ ์๊ฐ์ ๋๋ฌํ์ฌ ์์ํ๊ฒ ๋๋ค๋ฉด ์ด ๋น 20๊ฐ์ ์์ฒญ์ ์งํํ๊ฒ ๋๋ค.
ํ์ง๋ง ์ ๋ฐฉ์์ ๋ฌธ์ ์ ์ผ๋ก๋ ๊ฐ ๋ก์ง๋ง๋ค ๋๊ธฐํ๊ฐ ์ ๋๋ก ์ด๋ฃจ์ด์ง์ง ์๋ค๋ ๊ฒ์ด๋ค. ์๋ฅผ ๋ค์ด์ 1๋ถ ์ฃผ๊ธฐ๋ก ๋ณ๋๋ฅ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฐ์ดํธํ๋ ๋ก์ง์ ์งํํ์ ๋ ๋์์ ์กฐํ์ ์ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฐ์ดํธ๋ฅผ ํ๊ฒ ๋๋ค๋ฉด, ๊ฐ ๋ก์ง์์ ์์ฒญ์ด ์ด๋ ์ ๋ ์ผ์ด๋๋์ง ์ ์ ์๊ธฐ ๋๋ฌธ์ ๊ฒฐ๊ตญ ์กฐ์ ์ ์คํจํ์ฌ ์๋ฌ๋ฅผ ๋ฐ๊ฒ ๋๋ค.
๋ ๋ฒ์งธ ๋ก์ง์ ์ฐจํธ ๋ฐ์ดํฐ์ ๊ฐ์ด ํ๋ฒ์ ๋ง์ ์์ฒญ์ ์ ์ดํ๊ธฐ ์ํด ์ฌ์ฉํ ๋ก์ง์ด๋ค. ํด๋น ๋ฐฉ์์ ํ์ฌ ๊ฐ์ง๊ณ ์๋ ๊ณ์ข๋ฅผ ์ต๋ํ ํ์ฉํด์ ๋ง์ ์์ฒญ์ ์งํํ๋ ๋ฐฉ์์ด๋ค. ๊ฐ์ง๊ณ ์๋ ๊ณ์ข์ ์๋งํผ Task chunk๋ฅผ ๋๋์ด์ setTimeout()์ ํตํด ์ผ์ ์๊ฐ์ด ์ง๋ ํ ์์ฒญ์ ์งํํ๋๋ก ํ๋ค.
ํ์ง๋ง ์ด๊ฒ๋ ์์ฒญ์ ์ ์ดํ๊ธฐ ํ๋ค์๋ค. ํด๋น ๋ก์ง์ ๋ฌธ์ ๋ ์ฒซ๋ฒ์งธ ๋ก์ง๊ณผ ๊ฐ์ด ๋ค๋ฅธ ์์ฒญ๊ณผ ๊ฒน์น๊ฒ ๋ ๋ ์์ฒญ์ด ์ด๊ณผ๋ ์ ์๋ ๋ฌธ์ ๊ฐ ์๊ณ , setTimeout์ ์ค์ฐจ๊ฐ ๋ง์ด ์์ฌ ์์ธกํ๊ธฐ ์ด๋ ค์ด ๋ฌธ์ ๊ฐ ์์๋ค. ๋ ํฐ ๋ฌธ์ ๋ก ๋๋ฌด ๋ง์ setTimout์ ์์๋์๊ธฐ ๋๋ฌธ์ ์๋ฒ์ ๋ฉ๋ชจ๋ฆฌ๊ฐ ํฐ์ง๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์๋ค. ๊ฒฐ๊ตญ ์ ํ๋ ์์ฒญ ์๋ฅผ ์ ์ ์ดํ๊ธฐ ์ํด์ ์๋ก์ด ๋ก์ง์ ๋ง๋ค ์ ๋ฐ์ ์์๋ค.
ํด๋น ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด์ ์ด๋ฅผ ์ ๊ด๋ฆฌํ ์ ์๋ ์ค๊ฐ ์ง์ ์ด ํ์ํ๊ณ , ์ฐ์ ์์ ํ๋ฅผ ์ค๊ฐ ์ง์ ์ผ๋ก ๋์ด ์์ฒญ ์๋ฅผ ์ ์ดํ ์ ์๋๋ก ํ๋ค. ๊ฐ์ ๋ ๊ตฌ์กฐ๋ ๋ค์๊ณผ ๊ฐ์ด ์์ฐ์ - ํ - ์๋น์ ํํ์ ๊ตฌ์กฐ๋ฅผ ๊ฐ๋๋ก ํ๋ค.
graph LR
P1(์ฐจํธ ๋ฐ์ดํฐ ์ค์ผ์ค๋ฌ) --> PriorityQueue
P2(์ฃผ๊ฐ ์์ธ ์ ๋ณด ์ค์ผ์ค๋ฌ) --> PriorityQueue
P3(์ค์๊ฐ ๋ฐ์ดํฐ ์์ฒญ) --> PriorityQueue
P4(์ค์๊ฐ ๋ฐ์ดํฐ ์์ฒญ) --> PriorityQueue
PriorityQueue --> C1(๊ณ์ข1)
PriorityQueue --> C2(๊ณ์ข2)
PriorityQueue --> C3(๊ณ์ข3)
PriorityQueue --> C4(๊ณ์ข4)
ํ ๋์ ์ฐ์ ์์ ํ๋ฅผ ์ ํํ ์ด์ ๋ ์ต๋ํ ์ค์๊ฐ ์ฑ์ ์ ์งํ๊ธฐ ์ํจ์ด๋ค. ๊ฐ ์์ฒญ์ ์ฌ์ฉ์์ ์ ์ฅ์์ ๋ฐ๋์ ์ค์๊ฐ ์ฑ์ ์ ์งํด์ผํ๋ ์์ฒญ๊ณผ ๋ ์ ์งํด์ผํ๋ ์์ฒญ์ด ์๋ค.
์๋ฅผ๋ค์ด์ ๋ณ๋๋ฅ ์ด๋ ์กฐํ์ ์์ ๋ฐ์ดํฐ ์์ฒญ์ ๊ฒฝ์ฐ ์ ๋ฐ์ดํธ๊ฐ ๊ธด ์ฃผ๊ธฐ๋ก ๋ฐ์ํ๊ธฐ ๋๋ฌธ์ ์ค์๊ฐ ์ฑ์ ์ฐ์ ์์๊ฐ ๋ฎ๋ค. ํ์ง๋ง, ํ์ฌ ์์ ์ ์ฃผ๊ฐ ์ ๋ณด์ ๊ฒฝ์ฐ ํด๋น ์์ ์ ๊ฐ๊ฒฉ์ ์ค์๊ฐ ์ฑ์ ์ฐ์ ์์๊ฐ ๋๋ค. ๋ฐ๋ผ์ ์ฐ์ ์์๊ฐ ๋์ ์์ฒญ์ ๋จผ์ ์งํํ๊ณ , ๋ฎ์ ์์ฒญ์ ๋์ค์ ์งํํ๊ธฐ ์ํด ์ฐ์ ์์ ํ๋ฅผ ๋์ ํ๋ค.
์ฐ์ ์์ฐ์๋ ์ฃผ์ API ๋ฐ์ดํฐ ์์ฒญ์ ์งํํ๋ ๊ฐ์ฒด๋ก ์์ฒญ Task๋ฅผ ํ์ ์ฝ์ ํ๋๋ก ํ๋ค.
- ์์ฒญ url - ์ฃผ๊ฐ API์ ์์ฒญํ url
- ์ฟผ๋ฆฌ - ์์ฒญ์ ํ์ํ ์ฟผ๋ฆฌ
- TR_ID - ๊ฑฐ๋ ๋ฒํธ
- callback - ์์ฒญ์ด ์๋ฃ๋ ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํ๋ ํจ์
์์ ๋์์๋ ํญ๋ชฉ์ ํตํด ํ์ ๋ ธ๋ ํ์ ์ ์ ํ์๋ค.
interface Json {
output: Record<string, string> | Record<string, string>[];
output1: Record<string, string>[];
output2: Record<string, string>[];
}
interface OpenapiQueueNodeValue {
url: string;
query: object;
trId: TR_ID;
callback: <T extends Json>(value: T) => Promise<void>;
}
์์ฐ์๋ ์์ฒญํ๊ณ ์ ํ๋ API์ ๋ง๋ ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด ํ์ ์ฝ์ ํ๊ฒ ๋๋ค.
this.openApiQueue.enqueue({
url: this.url,
query,
trId: TR_IDS.ITEM_CHART_PRICE,
callback: this.getLiveDataSaveCallback(stock.id!, period),
});
์์ฐ์์ ์์ฒญ์ ์ ์ฅํ๋ ์๋ฃ๊ตฌ์กฐ์ด๋ค. ์ด๋ ์์ฒญ์ด ์คํจํ๊ฑฐ๋ ์ฐ์ ์์๊ฐ ๋์ ์์ ์ ๋จผ์ ๋น ์ ธ๋๊ฐ ์ ์๋๋ก ๋ง๋ค์๋ค. ์ด์ ์ ๋ง๋ ์ ๋๋ฆญ ํ์ ์ ์ฐ์ ์์ ํ๋ฅผ ํตํด์ Api์์ฒญ์ ๋ํ ๋ก์ง์ด ์ํํ ์ ์๋๋ก ์ ์ฉ ํ๋ฅผ ๋ง๋ค์๋ค.
class OpenapiQueue {
private queue: PriorityQueue<OpenapiQueueNodeValue> = new PriorityQueue();
enqueue(value: OpenapiQueueNodeValue, priority?: number) {
if (!priority) priority = 2;
this.queue.enqueue(value, priority);
}
dequeue(): OpenapiQueueNodeValue | undefined {
return this.queue.dequeue();
}
isEmpty(): boolean {
return this.queue.isEmpty();
}
}
์ฐ์ ์์ ํ๋ก๋ถํฐ ์์ฒญ์ ๊ฐ์ ธ์ ์ฒ๋ฆฌํ๋ ๊ณณ์ผ๋ก ์ฃผ์ API๋ก๋ถํฐ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์จ ํ callback ํจ์์ ์ฒ๋ฆฌํ๋๋ก ํ๋ค. ์ด๋ ์ฃผ๊ธฐ์ ์ผ๋ก ํ๊ฐ ๋น์๋์ง ํ์ธํ๋ฉด์ ์งํํ๊ณ , ์๋ฌ๊ฐ ๋ฐ์ํ๋ฉด ๋ ๋์ ์ฐ์ ์์๋ก ์ฌ์์ฒญ์ ํ๋๋กํ๋ค.
class OpenapiConsumer {
private readonly REQUEST_COUNT_PER_SECOND = 20;
private isProcessing: boolean = false;
private currentTokenIndex = 0;
constructor(
private readonly queue: OpenapiQueue,
private readonly openapiTokenApi: OpenapiTokenApi,
@Inject('winston') private readonly logger: Logger,
) {
this.start();
}
async start() {
setInterval(() => this.consume(), 1000);
}
async consume() {
if (this.isProcessing) {
return;
}
while (!this.queue.isEmpty()) {
this.isProcessing = true;
await this.processQueueRequest();
await new Promise((resolve) => setTimeout(resolve, 1000));
}
this.isProcessing = false;
}
private async processQueueRequest() {
const tokenCount = (await this.openapiTokenApi.configs()).length;
for (let i = 0; i < tokenCount; i++) {
await this.processIndividualTokenRequest(this.currentTokenIndex);
if (!this.isProcessing) {
return;
}
this.currentTokenIndex = (this.currentTokenIndex + 1) % tokenCount;
}
}
private async processIndividualTokenRequest(index: number) {
for (let i = 0; i < this.REQUEST_COUNT_PER_SECOND; i++) {
const node = this.queue.dequeue();
if (!node) {
return;
}
this.processRequest(node, index);
}
}
private async processRequest(node: OpenapiQueueNodeValue, index: number) {
try {
const data = await getOpenApi(
node.url,
(await this.openapiTokenApi.configs())[index],
node.query,
node.trId,
);
await node.callback(data);
} catch (error) {
this.logger.warn(error);
this.queue.enqueue(node, 1);
}
}
}
ํด๋น ๊ตฌ์กฐ๋ฅผ ์ ์ฉํ ํ ์ด์ ์ ๋นํด์ ์์ฒญ ์ ์ด ์คํจ์ ๋ํ ์๋ฌ๊ฐ ํฌ๊ฒ ์ค์ด๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
- ๐ฉ FE ๊ธฐ์ ์ ํ์ด์
- โจ ์ฐจํธ์ ๋ฐ์ํ ๊ตฌํ๊ณผ useRef ํ์ ๋ฌธ์
- ๐ฃ ๋ถ๋ชจ ์์์ ์ํ์ ๋ฐ๋ผ ์์ ์์๋ ์คํ์ผ ๋ณํ ๋ถ์ฌํ๊ธฐ
- ๐ zod ๋์ ํ๊ธฐ
- ๐ useInfiniteQuery๋ฅผ ์ฌ์ฉํ ๊ทธ๋ํ ๋ฌดํ์คํฌ๋กค ๊ตฌํ
- ๐ซ ์ฌ์ฉ์์ ์์ ๋ณํ ์๋ ๊ทธ๋ํ ์คํฌ๋กค ๊ตฌํํ๊ธฐ
- ๐งช ์๋ง์ ๊ทธ๋ํ ๋ฐ์ดํฐ ์์ฒญ์ ์ด๋ป๊ฒ ์ค์ผ๊น
- ๐ ๋คํฌ๋ชจ๋์์ ์๋ก๊ณ ์นจ ์ ๋ผ์ดํธ๋ชจ๋๊ฐ ์ ๊น ๋ณด์ด๋ ๋ฌธ์
- ๐ ์น์์ผ์ ์ฑํ ๋ฐ์ดํฐ์ REST API์ ์ฑํ ๋ฐ์ดํฐ๋ฅผ ํจ๊ป ๊ด๋ฆฌํ๊ธฐ
- ๐ก BE ๊ธฐ์ ์ ํ ์ด์
- โ๏ธ Node WebSocket ํ๊ณ ๋ค๊ธฐ
- โ๏ธ TypeORM Datasource mock ๋ง๋ค๊ธฐ
- โ๏ธ oauth ID range ๋ฌธ์
- ๐ custom pipe์์ Nan์ด ๋ฐ์์ง๋ ๋ฌธ์
- ๐ช nest Websocket์ ์ธ์ ์ด ์๋๋ค๊ณ ?
- ๐ด nginx websocket ์ฐ๊ฒฐ ์ ๋ฌธ์ ๋ฐ์
- ๐ WebPush ๊ตฌํ
- ๐ง ์ฐ์ ์์ ํ๋ก ์์ฒญ ์ ์ดํ๊ธฐ
- ๐ websocket์ด ๋ฆ๊ฒ ํ ๋น๋์ด ๋ฐ์๋๋ ๋ฌธ์
- ๐ฅณ typeorm์ ์ด์ฉํ FCM ์๋ฆผ ์๋น์ค
- ๐ฆ ๋ค์ค ์ ์ ๋์์ฑ ์ ์ด โ ์ฑ๊ธํค, ๋ฎคํ ์ค
- ๐ ๊ทธ๋ํ ๋ฐ์ดํฐ๋ฅผ ์ค์๊ฐ์ผ๋ก ์ ๊ณตํ๊ธฐ์ํ ์ ๋ต
- ๐ ๏ธ ์ธํ๋ผ ๊ธฐ์ ์คํ ์ ํ ์ด์
- ๐ Ncloud ์ค์ ๊ณผ์
- ๐ ORM ๊ธฐ์ ์คํ ๋น๊ต
- ๐ค RabbitMQ๋ก ๋ถ์ฐ ์๋ฒ์๊ฒ ๋ฉ์์ง๋ฅผ ๋ถ๋ฐฐํ๊ธฐ
- ๐ข private DB ์๋ฒ์ ์ ์ํ์ง ๋ชปํ๋ ํ์
- ๐ 1์ฃผ์ฐจ ๋ฐํ
- ๐ 2์ฃผ์ฐจ ๋ฐํ
- ๐ 3์ฃผ์ฐจ ๋ฐํ
- ๐ 4์ฃผ์ฐจ ๋ฐํ
- ๐ 5์ฃผ์ฐจ ๋ฐํ
- ๐ ์ต์ข ๋ฐํ