<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Way to be gorgeous developer</title>
    <link>https://juzdalua.tistory.com/</link>
    <description>공부한 내용을 기록한 블로그입니다. 
잘못된 내용은 알려주시면 다시 공부하여 수정하겠습니다.</description>
    <language>ko</language>
    <pubDate>Tue, 14 Apr 2026 14:10:44 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Juzdalua</managingEditor>
    <image>
      <title>Way to be gorgeous developer</title>
      <url>https://tistory1.daumcdn.net/tistory/5177626/attach/57501e8d95cc4f10953d99c7bebf7378</url>
      <link>https://juzdalua.tistory.com</link>
    </image>
    <item>
      <title>대용량 쿼리 벌크 업데이트</title>
      <link>https://juzdalua.tistory.com/338</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;그동안 잘못 알고 있었던 사실.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DB에 최소한으로 접근하는 것이 항상 좋다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; DB에 연결된 트랜잭션의 시간이 길어진다면, 전체적인 데이터베이스의 접근하는 속도가 저하된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 위 의미대로라면 분할로 업데이트하는 청크는 설명될 수 없다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NestJS, MySQL, Typeorm의 예를 들어보겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 업데이트에 필요한 행만큼 쿼리문 실행&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1771487028057&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;async bulkMap(dto: BulkDto): Promise&amp;lt;{ updatedCount: number }&amp;gt; {
	let updatedCount: number = 0;
    
    await this.entityManager.transaction(async (em) =&amp;gt; {
    	for(const item of dto.items){
        	const result = await em..createQueryBuilder()
          		.update(User)
                ...
                .excute();
                
               updatedCount += result.affected || 0;
        }
    });
    
    return { updatedCount };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드는 간단하지만 행이 많아질수록 트랜잭션 시간이 길어져 응답속도가 느려진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. DB 한번 연결 후 한방에 실행&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번의 소스코드에서 반복문에 조건절을 배열로 쌓고 최종적으로 DB에 한번에 업데이트하는 쿼리를 상상해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;행이 많아지면 많아질수록 트랜잭션의 시간은 길어진다.&lt;/li&gt;
&lt;li&gt;락이 걸려있다면 해당 DB에는 다른 커넥션이 접근이 불가능하다.&lt;/li&gt;
&lt;li&gt;SQL 패킷 길이 제한에 걸릴 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 청크로 분할 업데이트&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1771487706261&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;async bulkMap(dto: BulkDto): Promise&amp;lt;{ updatedCount: number }&amp;gt; {
	let updatedCount: number = 0;
    
    await this.entityManager.transaction(async (em) =&amp;gt; {
		// 데이터를 보관할 임시 테이블 생성
		await em.query(`
        	CREATE TEMPORARY TABLE tmp_user (
                user_id BIGINT NOT NULL PRIMARY KEY,
                json_obj JSON NOT NULL
            )
      	`);
        
        const chunkSize = 1000;
        for (let i = 0; i &amp;lt; dto.items.length; i += chunkSize) {
            const chunk = dto.items.slice(i, i + chunkSize);
            const values: string[] = [];
            const params: any[] = [];

            for (const item of chunk) {
          		const data = dataMap.get(item.userId);
          		values.push('(?, ?)');
          		params.push(item.userId, JSON.stringify([data]));
            }
      
      		// 임시테이블에 청크로 변경할 데이터 삽입
            await em.query(
                `INSERT INTO tmp_user (user_id, json_obj) VALUES ${values.join(', ')}`,
                params
            );
    	}  
        
        // 유저테이블 업데이트
		const userResult = await em.query(`
            UPDATE user u
            JOIN tmp_user t ON t.user_id = b.id
            SET u.json_obj = t.json_obj
      	`);
        
        // 주문테이블 업데이트
        const purchaseOrderUserResult = await em.query(`
            UPDATE purchase_order_user pou
            JOIN tmp_user t ON t.user_id = pou.user_id
            SET pou.json_obj = t.json_obj
  		`);
        
        // 수행 쿼리수 기록
  		updatedCount += (userResult?.affectedRows || 0) + (purchaseOrderUserResult?.affectedRows || 0);
        
        // 임시 테이블 삭제
  		await em.query('DROP TEMPORARY TABLE IF EXISTS tmp_buyer_sales_manager');
	});
    
    return { updatedCount };
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;임시테이블을 만든다&lt;/li&gt;
&lt;li&gt;변경할 데이터를 임시테이블에 청크 단위로 삽입한다&lt;/li&gt;
&lt;li&gt;변경할 테이블과 임시테이블을 조인하여 한번에 업데이트한다.&lt;/li&gt;
&lt;li&gt;임시테이블을 삭제한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법으로 10만건의 데이터를 업데이트하는데 12초밖에 걸리지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 쿼리 수행수를 줄이는 것이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DB접근을 줄이는 것도 좋지만, 수행해야 할 양이 많다면 청크로 진행하되 쿼리 숫자를 줄여보자.&lt;/b&gt;&lt;/p&gt;</description>
      <category>Server/성능개선</category>
      <author>Juzdalua</author>
      <guid isPermaLink="true">https://juzdalua.tistory.com/338</guid>
      <comments>https://juzdalua.tistory.com/338#entry338comment</comments>
      <pubDate>Thu, 19 Feb 2026 16:58:50 +0900</pubDate>
    </item>
    <item>
      <title>2025년 회고</title>
      <link>https://juzdalua.tistory.com/337</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;조금은 늦었지만 2025년 회고를 작성해본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;25년도에 맞이한 가장 큰 변화는 이직이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그동안 30인 이하의 스타트업에서만 근무를 했었고, 25년 7월에 총 사원수 300명에 육박하는 기업에 입사했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 속한 팀의 팀원은 20명 정도였고, 개발파트 인원은 나 포함 3명이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작은 기업에서도 항상 개발팀의 인원은 많았고, 입지가 두터웠다면 현재의 회사에서는 그와 정반대의 느낌이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이직 하위의 가장 큰 변화는 세 가지가 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 내가 만든 제품을 사용하는 사용자가 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 회사를 거치면서 많은 프로젝트를 진행했지만 항상 사용자는 거의 존재하지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 문제점이 있을까, 어떻게 해결해야될까는 늘 추상적이고 글로만 접했으며 개인프로젝트에서만 해결했었던 사건들이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금은 라이브 로그를 가만히 켜놓기만해도 터미널이 멈추질 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 얼마나 큰 기쁨인지, 원동력이 되는지 느끼게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 수치화할만한 프로젝트를 경험했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;남들의 이력서에서만 봐왔던 것들이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤작업을 해서 API 응답속도를 몇초, 몇% 단축했다가 가장 큰 예가 될 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그동안은 개인 프로젝트에서 생기지 않을 상황을 강제적으로 만들고 그걸 해결해왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 실무에서 매출, 성능 등 수치화할만한 프로젝트를 무사히 완수했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제서야 이력서에 진짜 이력을 한 줄 추가할 수 있게 됐구나 하는 기분이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 그래서 바쁘다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작은 에러부터 큰 에러까지 웹훅을 받아 모니터링을 진행한다. 그래서 발생하는 유지보수가 첫번째.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SR이라는 시스템 리퀘스트를 받아 추가개발 또는 기능/결함을 개선하는 작업을 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 프로젝트를 작업하고 있더라도 긴급 SR이 들어오면 프로젝트를 병행하거나 순서를 뒤바꿔야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벌써 입사 6개월차인데 하루하루 시간이 정말 빨리간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아마 오래오래 열심히 다닐 수 있을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연봉도, 인센도 많이많이 받자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올해도 더 열심히 잘하자.&lt;/p&gt;</description>
      <category>생각정리</category>
      <author>Juzdalua</author>
      <guid isPermaLink="true">https://juzdalua.tistory.com/337</guid>
      <comments>https://juzdalua.tistory.com/337#entry337comment</comments>
      <pubDate>Fri, 9 Jan 2026 18:10:52 +0900</pubDate>
    </item>
    <item>
      <title>Next 14 &amp;amp; React 18) 빌드에러</title>
      <link>https://juzdalua.tistory.com/336</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;오랜만에 넥스트를 돌렸는데 이상한 에러가 발생해서 해결하는데 이틀이나 소요됐다.&lt;/p&gt;
&lt;pre id=&quot;code_1767162128609&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Error: &amp;lt;Html&amp;gt; should not be imported outside of pages/_document.
TypeError: Cannot read properties of null (reading 'useState')

Error occurred prerendering page &quot;/_not-found&quot;.
Export encountered an error on /_not-found/page: /_not-found, exiting the build.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이외에 여러 에러도 만나봤지만, 해결했던 결론만 먼저 이야기하고 시작하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나의 경우 아래를 확인하니 해결됐다.&lt;/p&gt;
&lt;pre id=&quot;code_1767162186618&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;echo $NODE_ENV
# development&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;진행중인 백엔드 프로젝트에서 환경변수 고정이 필요했기에 development로 NODE_ENV를 고정해놓았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드는 프로덕션에 배포용으로 만드는 작업인데 현재 환경변수가 개발환경이니 에러가 발생한 것이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트 개발자가 아니라 자세히는 모르지만 무언가 엉킨 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 빌드 명령어를 수정하거나 저 환경변수를 지워버리면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나의 경우는 환경변수가 필요하기에 넥스트 프로젝트의 빌드시 환경변수만 직접 고정해놓았다.&lt;/p&gt;
&lt;pre id=&quot;code_1767162239630&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// package.json

// &quot;build&quot;: &quot;next build&quot;,
&quot;build&quot;: &quot;NODE_ENV=production next build&quot;,&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1767162330843&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;unset NODE_ENV&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 에러메세지가 훅에 관한 것들이기에 모든 패턴을 맞춰보았지만 동일한 에러로 해결이 되지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 해결하면서 참 별의 별 것들을 해보았고 배운 것도 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 넥스트15와 리액트19를 사용중이었는데 버전호환이 되지 않아 다운그레이드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. &quot;use client&quot;를 선언하지 않으면 기본적으로 서버컴포넌트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 훅을 사용하는 곳에 &quot;use client&quot;를 작성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 클라이언트 컴포넌트에는 async로 생성 불가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 공통 레이아웃이 아닌 라우터별 레이아웃 적용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 당연한 사실도 있고 의미없는 것들도 있지만 모두 적용해보아도 해결이 되지 않았고 최후에는 브랜치를 새로 파서 쌩프로젝트를 빌드해보자고 마음먹었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;넥스트 크리에이트 앱을 한 후 빌드를 하니 터지길래 어이가 없어 찾아본 결과 환경변수 문제인걸 확인했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드 개발자로 프론트 사이드 프로젝트가 터지니 멘탈도 같이 터졌는데 개발세상은 참 어렵고 배울게 천지인 것 같다.&lt;/p&gt;</description>
      <category>Client/Next.js</category>
      <author>Juzdalua</author>
      <guid isPermaLink="true">https://juzdalua.tistory.com/336</guid>
      <comments>https://juzdalua.tistory.com/336#entry336comment</comments>
      <pubDate>Wed, 31 Dec 2025 15:29:49 +0900</pubDate>
    </item>
    <item>
      <title>JS &amp;amp; TS) Map으로 시간복잡도 줄이기</title>
      <link>https://juzdalua.tistory.com/335</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;C++에서는 map, set을 엄청 자주 사용했는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JS에서는 이상하게 사용을 안하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러다 반복문에서 include, find 등 람다식을 사용해 이중포문을 만들게 되었고.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5만건의 유저 업데이트 중, O(n2)이라는 무시무시한 경험을 해버렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 부랴부랴 C++에서 사용했던 맵을 다시 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1765360209312&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const users: User[] = await this.userRepo.find();

// new Map
const userMap: Map&amp;lt;number, User&amp;gt; = new Map&amp;lt;number, User&amp;gt;(users.map((u) =&amp;gt; [u.id, u]));

// has
if(userMap.has(1)) console.log('id 1인 유저 포함');

// get
const user1 = userMap.get(1);

// set
userMap.set(2,{id: 2, name: 'jun'});

// delete
userMap.delete(2);

// for
for(const key of userMap.keys()){}
for(const value of userMap.values()){}
for(const [key, value] of userMap.entries()){}

// size
console.log(userMap.size)&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Server/성능개선</category>
      <author>Juzdalua</author>
      <guid isPermaLink="true">https://juzdalua.tistory.com/335</guid>
      <comments>https://juzdalua.tistory.com/335#entry335comment</comments>
      <pubDate>Wed, 10 Dec 2025 18:51:39 +0900</pubDate>
    </item>
    <item>
      <title>평균 15s API 응답속도 2s로 줄이기</title>
      <link>https://juzdalua.tistory.com/334</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;문제상황&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 구매목록을 가져오는 API가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;총 구매목록은 2만여건이지만, 조인된 테이블들의 데이터 가짓수를 합치면 60만여건이 넘었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DTO를 활용하여 모든 테이블을 조인하고 테이블별 검색조건을 where절로 한 번에 검색할 수 있는 편리함이 있었지만, 검색을 하지 않아도 되는 상태에서도 모든 테이블을 조인하는 문제가 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 해당 페이지에 진입하면 모든 구매목록을 가져오기 때문에 15초라는 긴 시간이 소요됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;해결방법&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리스트 페이지는 디테일 정보를 표기할 필요가 없으므로, &lt;u&gt;&lt;b&gt;필요없는 테이블은 조인에서 제외한다&lt;/b&gt;&lt;/u&gt;.&lt;/li&gt;
&lt;li&gt;DTO에 따라 검색조건이 들어오면, 필&lt;u&gt;&lt;b&gt;요한 테이블만 조인한다.&lt;/b&gt;&lt;/u&gt;&lt;/li&gt;
&lt;li&gt;인덱스를 탈 수 없는 LIKE 검색은 최소화한다.&lt;/li&gt;
&lt;li&gt;검색조건으로 사용되는 컬럼들은 인덱싱을 설정한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;사이드 이펙트&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해당 API에서 사용하는 함수가 공통함수일 경우, 필요한 정보를 가져오는 테이블들이 모두 만족하는지 확인해야한다.&lt;/li&gt;
&lt;li&gt;검색 조건을 DTO로 넘기고 해당 DTO를 바로 where절에 넘기는 방식이었다면, 정렬과 검색이 제대로 이루어지는지 확인해야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Server/성능개선</category>
      <author>Juzdalua</author>
      <guid isPermaLink="true">https://juzdalua.tistory.com/334</guid>
      <comments>https://juzdalua.tistory.com/334#entry334comment</comments>
      <pubDate>Fri, 17 Oct 2025 11:43:02 +0900</pubDate>
    </item>
    <item>
      <title>MySQL, TypeORM) Lock으로 동시성 문제 해결</title>
      <link>https://juzdalua.tistory.com/333</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;전제조건&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;product 테이블에는 stock이라는 재고가 있다.&lt;/li&gt;
&lt;li&gt;재고는 0이 되면 구매할 수 없다.&lt;/li&gt;
&lt;li&gt;a, b 유저가 동시에 동일한 상품을 구매한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로직&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;nbsp;a, b유저는 재고가 존재하는 상품의 정보를 불러온다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1760668349205&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT * FROM product WHERE id = 1 AND stock &amp;gt; 0;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 상황&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;a가 먼저 상품을 구매하고 재고를 차감하여 재고가 0이 되면, b는 불러왔던 상품을 구매할 수 없다.&lt;/li&gt;
&lt;li&gt;트랜잭션을 사용했지만 두 유저가 동시에 테이블에 접근하는건 막을 수 없었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결 방법&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상품을 읽을 때 부터 Lock을 걸어 해당 행을 다른 유저가 가져올 수 없게 만든다.&lt;/li&gt;
&lt;li&gt;Lock을 건 상황에서 에러가 발생했다면, 빠르게 락을 해제해야한다. 무한히 기다리는 데드락이 발생할 수 있기 때문.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1760668508464&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT * FROM product WHERE id = 1 AND stock &amp;gt; 0 FOR UPDATE;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1760668579276&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const qb = await entityManager
      .getRepository(Product)
      .createQueryBuilder('p')
      .setLock('pessimistic_write')
      .where('p.stock &amp;gt; 0')
      .andWhere('p.id = 1')
      );

return qb.getMany();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Server/성능개선</category>
      <author>Juzdalua</author>
      <guid isPermaLink="true">https://juzdalua.tistory.com/333</guid>
      <comments>https://juzdalua.tistory.com/333#entry333comment</comments>
      <pubDate>Fri, 17 Oct 2025 11:36:34 +0900</pubDate>
    </item>
    <item>
      <title>NestJS) AWS-S3 효율적인 업로드 방법</title>
      <link>https://juzdalua.tistory.com/332</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;배경&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;회원가입시 이미지를 s3에 업로드한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방법 1&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프론트에서 이미지를 인풋태그에 삽입하면, 블롭으로 이미지를 만들어 미리보기를 만든다.&lt;/li&gt;
&lt;li&gt;서버에 formData로 바로 전송해 하나의 API에서 검증 및 S3업로드, 다른 로직을 수행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하나의 API에서 작업하니 편리하다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;바디를 formData로 받아야한다.&lt;/li&gt;
&lt;li&gt;스웨거 작성 등 직접 나열해야 하는 이유로 불편하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방법 2&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프론트에서 이미지를 인풋태그에 삽입 시, API를 한 번 호출한다.&lt;/li&gt;
&lt;li&gt;API에서는 getSignedUrlPromise 함수로 만료시간이 존재하는 임시 이미지를 버킷에 생성한다.&lt;/li&gt;
&lt;li&gt;해당 url과 파일 정보를 받아 프론트에서 이미지 태그로 미리보기를 만든다.&lt;/li&gt;
&lt;li&gt;서버에 json 바디로 파일정보를 가입할 회원의 정보와 함께 송신한다.&lt;/li&gt;
&lt;li&gt;서버에서는 회원 검증을 마치고, 임시 버킷의 이미지를 메인 버킷으로 복제한다.&lt;/li&gt;
&lt;li&gt;임시 버킷의 이미지를 삭제한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;바디를 json으로 통일할 수 있다.&lt;/li&gt;
&lt;li&gt;파일 업로드로 인한 최종 API의 시간을 단축할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;API와 두 번 이상 통신한다&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1754549515450&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import AWS from 'aws-sdk';

private s3: AWS.S3;

await this.s3.getSignedUrlPromise(); // 임시 이미지 생성
await this.s3.copyObject().promise(); // 임시 이미지를 메인 버킷에 복제
await this.s3.deleteObject().promise(); // 임시 이미지 삭제&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드 입장에서는 2번 방법이 매우 효율적인 것 같다.&lt;/p&gt;</description>
      <category>Server/성능개선</category>
      <author>Juzdalua</author>
      <guid isPermaLink="true">https://juzdalua.tistory.com/332</guid>
      <comments>https://juzdalua.tistory.com/332#entry332comment</comments>
      <pubDate>Thu, 7 Aug 2025 15:54:31 +0900</pubDate>
    </item>
    <item>
      <title>vscode) 터미널 선택 색상 변경</title>
      <link>https://juzdalua.tistory.com/331</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;open default setting 으로 defaultSettings.json 파일은 read-only로 수정이 불가능하다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;유저세팅을 바꾸면 자동으로 오버라이드된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-08-06 오후 7.14.35.png&quot; data-origin-width=&quot;612&quot; data-origin-height=&quot;118&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vuX8W/btsPJGjHYKL/5eJQJncVtHBsWnuWyUQ6RK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vuX8W/btsPJGjHYKL/5eJQJncVtHBsWnuWyUQ6RK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vuX8W/btsPJGjHYKL/5eJQJncVtHBsWnuWyUQ6RK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvuX8W%2FbtsPJGjHYKL%2F5eJQJncVtHBsWnuWyUQ6RK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;612&quot; height=&quot;118&quot; data-filename=&quot;스크린샷 2025-08-06 오후 7.14.35.png&quot; data-origin-width=&quot;612&quot; data-origin-height=&quot;118&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1754475377347&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
	&quot;workbench.colorCustomizations&quot;: {
        &quot;terminal.selectionBackground&quot;: &quot;#FFD700&quot;
      }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가하여 작성하고 리로드하면 터미널 선택색상이 변경되어 가시성이 좋아진다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-08-06 오후 7.17.06.png&quot; data-origin-width=&quot;315&quot; data-origin-height=&quot;24&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u7Ork/btsPK3d5GH4/DGgzQvO5sHjZiX1xjAcKwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u7Ork/btsPK3d5GH4/DGgzQvO5sHjZiX1xjAcKwK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u7Ork/btsPK3d5GH4/DGgzQvO5sHjZiX1xjAcKwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu7Ork%2FbtsPK3d5GH4%2FDGgzQvO5sHjZiX1xjAcKwK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;315&quot; height=&quot;24&quot; data-filename=&quot;스크린샷 2025-08-06 오후 7.17.06.png&quot; data-origin-width=&quot;315&quot; data-origin-height=&quot;24&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Settings/Setting</category>
      <author>Juzdalua</author>
      <guid isPermaLink="true">https://juzdalua.tistory.com/331</guid>
      <comments>https://juzdalua.tistory.com/331#entry331comment</comments>
      <pubDate>Wed, 6 Aug 2025 19:17:18 +0900</pubDate>
    </item>
    <item>
      <title>NestJS) 의존성주입에서 객체 초기화 문제</title>
      <link>https://juzdalua.tistory.com/330</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 백그라운드를 정해본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. AppModule에 임포트 순서대로 모듈의 초기화가 진행된다.&lt;/p&gt;
&lt;pre id=&quot;code_1754471237224&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import {
  EnvConfigModule,
  TypeOrmConfigService,
} from './config';

import { AuthModule } from './modules/auth/auth.module';
import { AdminModule } from './modules/admin/admin.module';

@Module({
  imports: [
    EnvConfigModule, // 환경변수를 생성해야하므로 가장 처음에 위치해야한다.
    AuthModule, // 2번째로 초기화
    AdminModule, // 3번째로 초기화
    TypeOrmModule.forRootAsync({ useClass: TypeOrmConfigService }), // webpack 실행시 entities 참조를 위해 데이터베이스 엔티티 모듈들 아래에 위치해야한다.
  ],
  controllers: [AppController],
  providers: [],
})
export class AppModule  {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. main.ts에 작성하면 AppModule 초기화 이전, 의존성 주입 전에 먼저 실행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 서로 의존성을 주입하는 모듈의 경우, forwadRef를 통한 순환참조 문제를 해결할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. NestJS의 의존성주입은 다음 순서로 이루어진다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모듈 객체 생성&amp;nbsp;&lt;/li&gt;
&lt;li&gt;생성자 콜 + 각 모듈의 provider에 명시된 객체들만 의존성 주입 (동시에 발생)&lt;/li&gt;
&lt;li&gt;OnModuleInit 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;전제상황&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. DB에 접근하여 CommonCode 파일을 불러와 내부에 커먼코드 파일을 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 물리적 로직은 커먼코드파일을 기반으로 엔티티 및 환경변수를 셋팅한다. 세팅이 끝나면 서버를 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 커먼코드를 기반으로 엔티티를 생성하는 모듈은 생성자에 DB에 다녀오는 로직을 작성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 생성자에서는 비동기함수를 실행할 수 없다. 따라서 DB에 다녀오라는 함수를 콜하고 생성자는 종료된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 해당 모듈이 의존성으로 주입되려는 순간, 아직 DB 작업이 완료되지 않아 커먼코드 기반 엔티티를 생성할 수 없어 에러가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1754471832165&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Injectable()
export class SetService{
	constructor(
    	@Inject(forwardRef(() =&amp;gt; AppService))
    	private readonly appService: AppService, // 에러 발생
    ){
    	this.initDB().then(() =&amp;gt; {
        	console.log(&quot;DONE INIT SET&quot;);
        });
    }
    
    private async initDB(){
    	// ... DB 연결 및 쿼리 로직
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1754472914093&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Injectable()
export class AppService{
	constructor(
    	@Inject(forwardRef(() =&amp;gt; SetService))
    	private readonly setService: SetService, // 에러 발생
    ){
    	this.initDB().then(() =&amp;gt; {
        	console.log(&quot;DONE INIT APP&quot;);
        });
    }
    
    private async initDB(){
    	// ... DB 연결 및 쿼리 로직
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 부팅시 로그를 살펴보면, &quot;DONE INIT ...&quot;이 가장 마지막쯤에 찍힌다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 모듈 객체의 생성자 함수를 실행하고 의존성 주입이 이루어지는데, 생성자에서는 비동기 함수를 사용할 수 없어 각 객체가 생성자함수를 실행했을 뿐 작업을 완료하지는 못했기 때문이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;해결방법&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1754473216674&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Injectable()
export class SetService{

	private static appService: AppService;

	constructor(){
    	this.initDB().then(() =&amp;gt; {
        	console.log(&quot;DONE INIT SET&quot;);
        });
    }
    
    private async initDB(){
    	// ... DB 연결 및 쿼리 로직
    }
    
    public static setAppService(instance: AppService){
    	this.appService = instance;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1754473288867&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// app.module.ts

...
export class AppModule implements OnModuleInit {
  constructor(private appService: AppService) {}
  onModuleInit() {
    SetService.setAppService(this.appService);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱모듈에 의존성을 강제로 주입한 뒤, 해당 객체를 내부 변수로 세터를 활용하여 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트를 위해 스태틱 메소드를 생성했고, 앱모듈이 시작될 때 직접 초기화를 해준다.&lt;/p&gt;</description>
      <category>Server/NodeJS &amp;amp; NestJS</category>
      <author>Juzdalua</author>
      <guid isPermaLink="true">https://juzdalua.tistory.com/330</guid>
      <comments>https://juzdalua.tistory.com/330#entry330comment</comments>
      <pubDate>Wed, 6 Aug 2025 18:47:14 +0900</pubDate>
    </item>
    <item>
      <title>Git) 계정 2개 같은 호스트로 사용하기</title>
      <link>https://juzdalua.tistory.com/329</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://juzdalua.tistory.com/5&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://juzdalua.tistory.com/5&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1754041515332&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Git 여러 계정 사용하기&quot; data-og-description=&quot;회사계정, 개인계정을 하나의 컴퓨터에서 사용하다보면 repository에 접근하기가 어려워진다.이를 위한 해결책은 bash에서 ssh key를 이용한 방법이 있다.&amp;nbsp;1. rsa 키 만들기ssh-keygen -t rsa -C &amp;quot;깃헙계정@em&quot; data-og-host=&quot;juzdalua.tistory.com&quot; data-og-source-url=&quot;https://juzdalua.tistory.com/5&quot; data-og-url=&quot;https://juzdalua.tistory.com/5&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/JFyL0/hyZrplPoAG/AtwE1h119rELzptsu4p4z1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/GwwSM/hyZuBrDAHJ/BWG6BsYKa5SCAac53zYTf1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bYjOyX/hyZqPLRuAN/9sJ8AfQcvphwk6nqizk6k0/img.png?width=315&amp;amp;height=772&amp;amp;face=0_0_315_772&quot;&gt;&lt;a href=&quot;https://juzdalua.tistory.com/5&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://juzdalua.tistory.com/5&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/JFyL0/hyZrplPoAG/AtwE1h119rELzptsu4p4z1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/GwwSM/hyZuBrDAHJ/BWG6BsYKa5SCAac53zYTf1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bYjOyX/hyZqPLRuAN/9sJ8AfQcvphwk6nqizk6k0/img.png?width=315&amp;amp;height=772&amp;amp;face=0_0_315_772');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Git 여러 계정 사용하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;회사계정, 개인계정을 하나의 컴퓨터에서 사용하다보면 repository에 접근하기가 어려워진다.이를 위한 해결책은 bash에서 ssh key를 이용한 방법이 있다.&amp;nbsp;1. rsa 키 만들기ssh-keygen -t rsa -C &quot;깃헙계정@em&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;juzdalua.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 글은 2달차 신입이 작성한 글이라 지금 다시 봐도 어지럽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 다시 작성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 깃 계정 2개가 필요하다. 회사계정 1개, 개인 계정 1개라 가정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계정1: a@a.a&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계정2: b@b.b&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각 키를 만들고, 각 계정의 Git ssh 정보를 입력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ssh키의 정보는 .pub 파일이 아닌 확장자가 명시되지 않은 파일의 내용을 전부 복사하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1754041668922&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ssh-keygen -t rsa -C &quot;a@a.a&quot; -f id_rsa_a # id_rsa_a 파일에 암호정보 있음.
ssh-keygen -t rsa -C &quot;b@b.b&quot; -f id_rsa_b # id_rsa_b 파일에 암호정보 있음.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;깃 로그인 -&amp;gt; 깃 프로필 사진 -&amp;gt; setting -&amp;gt; SSH and GPG keys&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;id_rsa_a / id_rsa_a.pub / id_rsa_b / id_rsa_b.pub 파일 4개롤 ~/.ssh 폴더로 이동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;폴더가 없으면 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;config 파일 작성.&lt;/p&gt;
&lt;pre id=&quot;code_1754041727642&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;code ~/.ssh/config&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1754041758072&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# a 계정
Host github.com
        HostName github.com
        User git
        IdentityFile ~/.ssh/id_rsa_a

# b 계정
Host github.com
        HostName github.com
        User git
        IdentityFile ~/.ssh/id_rsa_b&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호스트가 같으므로 한번에 두개의 키를 동시에 사용할 수 없다. 있지만 푸쉬할 때 키를 순회한다고 하기 때문에 하지 않을거다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 터미널에 작성하기 귀찮으므로 스크립트 파일을 만들어 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원하는 위치에 생성.&lt;/p&gt;
&lt;pre id=&quot;code_1754041880325&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;vim ssh-a&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1754041913616&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ssh-add -D
ssh-add ~/.ssh/id_rsa_a
git config --global user.name &quot;a&quot;
git config --global user.email &quot;a@a.a&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일하게 b 스크립트 파일도 만들어준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 명령어를 작성해보자면 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1754042016241&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# a 키 등록
ssh-add id_rsa_a

# 에러가 발생한다면 아래를 입력하고 키 다시 등록
eval $(ssh-agent)

# 등록된 키 삭제
ssh-add -D

# 등록된 키 조회
ssh-add -l

# 등록된 키에 대한 사용자명을 &quot;a&quot;로 변경
git config --global user.name &quot;a&quot;

# 등록된 키에 대한 사용자 email을 변경
git config --global user.email &quot;a@a.a&quot;

# 현재 등록된 키에 대한 사용자 정보
git config --list&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;a로 커밋/푸시를 하고 싶다면 아래처럼 입력 후 커밋/푸시&lt;/p&gt;
&lt;pre id=&quot;code_1754042049809&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;./ssh-a&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;b계정도 이와 동일하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같은 에러가 발생한다면, 권한을 조정하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1754042279936&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Permissions are too open / WARNING: UNPROTECTED PRIVATE KEY FILE!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1754042275066&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;chmod 600 ~/.ssh/id_rsa_a
chmod 644 ~/.ssh/id_rsa_a.pub
chmod 700 ~/.ssh&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Settings/Git</category>
      <author>Juzdalua</author>
      <guid isPermaLink="true">https://juzdalua.tistory.com/329</guid>
      <comments>https://juzdalua.tistory.com/329#entry329comment</comments>
      <pubDate>Fri, 1 Aug 2025 18:59:02 +0900</pubDate>
    </item>
  </channel>
</rss>