<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>같이 연구해요</title>
    <link>https://researchwithme.tistory.com/</link>
    <description>인생과 경험을 녹여냅니다.</description>
    <language>ko</language>
    <pubDate>Fri, 24 Jul 2026 03:04:23 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>글쓰는 연구원 상티</managingEditor>
    <image>
      <title>같이 연구해요</title>
      <url>https://tistory1.daumcdn.net/tistory/6110873/attach/d2f4e7dacb3b477eb9ef41bbe3a26899</url>
      <link>https://researchwithme.tistory.com</link>
    </image>
    <item>
      <title>AI혁신 솔루션센터 개발 여정: 모노레포 아키텍처로 시작하는 엔터프라이즈 플랫폼 구축기</title>
      <link>https://researchwithme.tistory.com/14</link>
      <description>&lt;h2&gt;프롤로그: 예상치 못한 리더십의 시작&lt;/h2&gt;
&lt;p&gt;대학원 과정 중 글로컬 대학 선정이라는 예상치 못한 기회가 찾아왔다. AI혁신 솔루션센터 개발 프로젝트의 총괄을 맡게 되면서, 나는 단순한 개발자에서 아키텍트이자 프로젝트 리더가 되어야 했다. 처음에는 막막했다. 여러 프로덕트를 동시에 개발해야 하는 상황에서 어떻게 일관성 있고 확장 가능한 시스템을 구축할 것인가라는 근본적인 질문에 직면했기 때문이다.&lt;/p&gt;
&lt;h2&gt;1장: 아키텍처의 선택 - 왜 모노레포인가?&lt;/h2&gt;
&lt;h3&gt;문제 정의: 복잡성의 늪&lt;/h3&gt;
&lt;p&gt;초기 요구사항을 분석하면서 깨달은 것은 이 프로젝트가 단순한 웹 애플리케이션이 아니라는 점이었다. &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;AI Solution Center 요구사항:
├── 사용자 관리 시스템
├── AI 모델 서빙 플랫폼
├── 데이터 분석 대시보드
├── 실시간 채팅 서비스
├── 문서 관리 시스템
├── API 게이트웨이
└── 관리자 백오피스&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;각각이 독립적인 서비스로 운영되어야 하지만, 동시에 사용자 인증, 데이터베이스 접근, 로깅, 모니터링 등의 공통 기능을 공유해야 했다. 이때 마이크로서비스 아키텍처와 모노레포 사이에서 고민이 시작되었다.&lt;/p&gt;
&lt;h3&gt;모노레포 선택의 근거&lt;/h3&gt;
&lt;p&gt;여러 선택지를 놓고 심사숙고한 끝에 모노레포를 선택했다. 그 이유는 다음과 같았다:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 코드 재사용성과 일관성&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// 각 서비스에서 동일한 인터페이스 사용
import { BaseEntity, BaseRepository } from &amp;#39;@ai-solution/core/database&amp;#39;;
import { JwtAuthGuard, CurrentUser } from &amp;#39;@ai-solution/core/auth&amp;#39;;
import { LLMService } from &amp;#39;@ai-solution/core/llm&amp;#39;;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;2. 의존성 관리의 단순화&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;dependencies&amp;quot;: {
    &amp;quot;@nestjs/core&amp;quot;: &amp;quot;^10.0.0&amp;quot;,
    &amp;quot;typeorm&amp;quot;: &amp;quot;^0.3.17&amp;quot;,
    &amp;quot;passport-jwt&amp;quot;: &amp;quot;^4.0.1&amp;quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;모든 서비스가 동일한 버전의 의존성을 사용하여 호환성 문제를 원천 차단했다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 개발 효율성&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 전체 프로젝트 빌드
nx run-many --target=build --all

# 특정 라이브러리 변경 시 영향받는 프로젝트만 빌드
nx affected:build --base=main&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Nx 선택의 기술적 배경&lt;/h3&gt;
&lt;p&gt;Nx를 선택한 것은 단순히 모노레포 도구이기 때문만이 아니었다. &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// nx.json 설정
{
  &amp;quot;extends&amp;quot;: &amp;quot;@nx/workspace/presets/npm.json&amp;quot;,
  &amp;quot;$schema&amp;quot;: &amp;quot;./node_modules/@nx/workspace/schemas/nx-schema.json&amp;quot;,
  &amp;quot;targetDefaults&amp;quot;: {
    &amp;quot;build&amp;quot;: {
      &amp;quot;dependsOn&amp;quot;: [&amp;quot;^build&amp;quot;],
      &amp;quot;inputs&amp;quot;: [&amp;quot;production&amp;quot;, &amp;quot;^production&amp;quot;]
    },
    &amp;quot;test&amp;quot;: {
      &amp;quot;inputs&amp;quot;: [&amp;quot;default&amp;quot;, &amp;quot;^production&amp;quot;, &amp;quot;{workspaceRoot}/jest.preset.js&amp;quot;]
    }
  },
  &amp;quot;namedInputs&amp;quot;: {
    &amp;quot;default&amp;quot;: [&amp;quot;{projectRoot}/**/*&amp;quot;, &amp;quot;sharedGlobals&amp;quot;],
    &amp;quot;production&amp;quot;: [&amp;quot;default&amp;quot;, &amp;quot;!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)&amp;quot;, &amp;quot;!{projectRoot}/tsconfig.spec.json&amp;quot;, &amp;quot;!{projectRoot}/jest.config.[jt]s&amp;quot;, &amp;quot;!{projectRoot}/src/test-setup.[jt]s&amp;quot;, &amp;quot;!{projectRoot}/test-setup.[jt]s&amp;quot;, &amp;quot;!{projectRoot}/.eslintrc.json&amp;quot;, &amp;quot;!{projectRoot}/eslint.config.js&amp;quot;],
    &amp;quot;sharedGlobals&amp;quot;: []
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nx의 의존성 그래프 분석과 증분 빌드 기능은 대규모 프로젝트에서 빌드 시간을 획기적으로 줄여준다. 실제로 7개의 서비스와 3개의 코어 라이브러리가 있는 현재 구조에서도 변경된 부분만 빌드하여 전체 빌드 시간을 70% 이상 단축할 수 있었다.&lt;/p&gt;
&lt;h2&gt;2장: 코어 라이브러리 설계 - 공통 기능의 추상화&lt;/h2&gt;
&lt;h3&gt;아키텍처 설계 원칙&lt;/h3&gt;
&lt;p&gt;코어 라이브러리를 설계할 때 다음 원칙들을 세웠다:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;단일 책임 원칙&lt;/strong&gt;: 각 라이브러리는 하나의 명확한 책임을 가진다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;의존성 역전&lt;/strong&gt;: 구체적인 구현이 아닌 추상화에 의존한다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;개방-폐쇄 원칙&lt;/strong&gt;: 확장에는 열려있고 수정에는 닫혀있다&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;libs/core/
├── auth/          # 인증 및 권한 관리
├── database/      # 데이터베이스 추상화
└── llm/           # LLM 프로바이더 통합&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;core-auth: 보안의 중심축&lt;/h3&gt;
&lt;p&gt;인증 시스템을 설계하면서 가장 중요하게 생각한 것은 확장성과 보안성이었다. JWT 기반의 무상태 인증을 선택했지만, 동시에 미래의 요구사항 변화에 대비할 수 있도록 설계했다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// libs/core/auth/src/auth.service.ts
@Injectable()
export class AuthService {
  constructor(
    @InjectRepository(User)
    private userRepository: Repository&amp;lt;User&amp;gt;,
    private jwtService: JwtService,
    private configService: ConfigService,
  ) {}

  async validateUser(email: string, password: string): Promise&amp;lt;User | null&amp;gt; {
    try {
      const user = await this.userRepository.findOne({
        where: { email, isActive: true },
      });

      if (user &amp;amp;&amp;amp; (await user.validatePassword(password))) {
        return user;
      }
      return null;
    } catch (error) {
      throw new UnauthorizedException(&amp;#39;Authentication failed&amp;#39;);
    }
  }

  async login(email: string, password: string): Promise&amp;lt;LoginResponse&amp;gt; {
    const user = await this.validateUser(email, password);
    if (!user) {
      throw new UnauthorizedException(&amp;#39;Invalid credentials&amp;#39;);
    }

    // 토큰 생성
    const payload: JwtPayload = {
      sub: user.id,
      email: user.email,
      role: user.role,
    };

    const token = this.jwtService.sign(payload, {
      expiresIn: this.configService.get&amp;lt;string&amp;gt;(&amp;#39;JWT_EXPIRES_IN&amp;#39;, &amp;#39;1d&amp;#39;),
    });

    // 마지막 로그인 시간 업데이트
    await this.userRepository.update(user.id, {
      lastLoginAt: new Date(),
    });

    return {
      user: {
        id: user.id,
        email: user.email,
        firstName: user.firstName,
        lastName: user.lastName,
        role: user.role,
      },
      token,
    };
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;특히 주목할 점은 패스워드 검증 로직을 User 엔티티 내부로 캡슐화한 것이다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// libs/core/auth/src/entities/user.entity.ts
@Entity(&amp;#39;users&amp;#39;)
export class User {
  @PrimaryGeneratedColumn(&amp;#39;uuid&amp;#39;)
  id!: string;

  @Column({ unique: true })
  email!: string;

  @Column()
  @Exclude()
  password!: string;

  @BeforeInsert()
  @BeforeUpdate()
  async hashPassword() {
    if (this.password) {
      const saltRounds = 12;
      this.password = await bcrypt.hash(this.password, saltRounds);
    }
  }

  async validatePassword(password: string): Promise&amp;lt;boolean&amp;gt; {
    return bcrypt.compare(password, this.password);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이러한 설계로 비즈니스 로직과 데이터 접근 로직을 분리하면서도, 보안 요구사항을 만족시킬 수 있었다.&lt;/p&gt;
&lt;h3&gt;core-database: 데이터 접근의 표준화&lt;/h3&gt;
&lt;p&gt;데이터베이스 레이어를 추상화하면서 가장 고민했던 부분은 반복적인 CRUD 작업을 어떻게 효율적으로 처리할 것인가였다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// libs/core/database/src/entities/base.entity.ts
export abstract class BaseEntity {
  @PrimaryGeneratedColumn(&amp;#39;uuid&amp;#39;)
  id!: string;

  @CreateDateColumn({
    type: &amp;#39;timestamp with time zone&amp;#39;,
    default: () =&amp;gt; &amp;#39;CURRENT_TIMESTAMP&amp;#39;,
  })
  createdAt!: Date;

  @UpdateDateColumn({
    type: &amp;#39;timestamp with time zone&amp;#39;,
    default: () =&amp;gt; &amp;#39;CURRENT_TIMESTAMP&amp;#39;,
    onUpdate: &amp;#39;CURRENT_TIMESTAMP&amp;#39;,
  })
  updatedAt!: Date;

  @DeleteDateColumn({
    type: &amp;#39;timestamp with time zone&amp;#39;,
    nullable: true,
  })
  deletedAt?: Date;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;BaseEntity를 통해 모든 엔티티가 공통으로 가져야 할 필드들을 정의했다. 특히 소프트 삭제 기능을 기본으로 제공하여 데이터 손실을 방지했다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// libs/core/database/src/repository/base.repository.ts
export class BaseRepository&amp;lt;T extends BaseEntity&amp;gt; extends Repository&amp;lt;T&amp;gt; {
  async findByIdOrFail(id: string): Promise&amp;lt;T&amp;gt; {
    const entity = await this.findOne({
      where: { id } as FindOptionsWhere&amp;lt;T&amp;gt;,
    });

    if (!entity) {
      throw new Error(`Entity with id ${id} not found`);
    }

    return entity;
  }

  async createAndSave(entityData: DeepPartial&amp;lt;T&amp;gt;): Promise&amp;lt;T&amp;gt; {
    const entity = this.create(entityData);
    return this.save(entity);
  }

  async updateById(id: string, updateData: DeepPartial&amp;lt;T&amp;gt;): Promise&amp;lt;T&amp;gt; {
    const entity = await this.findByIdOrFail(id);
    Object.assign(entity, updateData);
    return this.save(entity);
  }

  async softDeleteById(id: string): Promise&amp;lt;UpdateResult&amp;gt; {
    return this.softDelete(id);
  }

  async bulkCreate(entitiesData: DeepPartial&amp;lt;T&amp;gt;[]): Promise&amp;lt;T[]&amp;gt; {
    const entities = this.create(entitiesData);
    return this.save(entities);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;BaseRepository를 통해 반복적인 데이터베이스 작업을 표준화했다. 이로 인해 각 서비스에서 비즈니스 로직에만 집중할 수 있게 되었다.&lt;/p&gt;
&lt;h3&gt;core-llm: AI 기능의 통합&lt;/h3&gt;
&lt;p&gt;AI 솔루션센터라는 프로젝트 특성상 다양한 LLM 모델을 사용해야 했다. 하지만 각 프로바이더마다 다른 API 형태와 응답 구조를 가지고 있어 이를 통합하는 것이 큰 도전이었다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// libs/core/llm/src/interfaces/llm-provider.interface.ts
export interface LLMProvider {
  name: string;
  isAvailable(): Promise&amp;lt;boolean&amp;gt;;
  generateCompletion(
    messages: LLMMessage[],
    options?: LLMProviderOptions
  ): Promise&amp;lt;LLMResponse&amp;gt;;
}

export interface LLMMessage {
  role: &amp;#39;system&amp;#39; | &amp;#39;user&amp;#39; | &amp;#39;assistant&amp;#39;;
  content: string;
}

export interface LLMResponse {
  content: string;
  usage?: {
    promptTokens: number;
    completionTokens: number;
    totalTokens: number;
  };
  provider: string;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;통합된 인터페이스를 정의한 후, 각 프로바이더별로 구현체를 만들었다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// libs/core/llm/src/providers/openai.provider.ts
@Injectable()
export class OpenAIProvider implements LLMProvider {
  public readonly name = &amp;#39;openai&amp;#39;;
  private client: OpenAI | null = null;

  constructor(private readonly apiKey?: string) {
    if (this.apiKey) {
      this.client = new OpenAI({ apiKey: this.apiKey });
    }
  }

  async generateCompletion(
    messages: LLMMessage[],
    options: LLMProviderOptions = {}
  ): Promise&amp;lt;LLMResponse&amp;gt; {
    if (!this.client) {
      throw new Error(&amp;#39;OpenAI client is not initialized&amp;#39;);
    }

    const completion = await this.client.chat.completions.create({
      model: options.model || &amp;#39;gpt-4o-mini&amp;#39;,
      messages: messages.map((msg) =&amp;gt; ({
        role: msg.role,
        content: msg.content,
      })),
      temperature: options.temperature ?? 0.7,
      max_tokens: options.maxTokens,
      top_p: options.topP,
    });

    const response = completion.choices[0];
    return {
      content: response.message?.content || &amp;#39;&amp;#39;,
      usage: completion.usage ? {
        promptTokens: completion.usage.prompt_tokens,
        completionTokens: completion.usage.completion_tokens,
        totalTokens: completion.usage.total_tokens,
      } : undefined,
      provider: this.name,
    };
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;가장 중요한 부분은 LLMService에서 구현한 자동 페일오버 기능이었다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// libs/core/llm/src/llm.service.ts
@Injectable()
export class LLMService {
  private providers: Map&amp;lt;string, LLMProvider&amp;gt; = new Map();
  private fallbackProviders: string[] = [];

  async generateCompletion(
    messages: LLMMessage[],
    options: LLMProviderOptions &amp;amp; { provider?: string } = {}
  ): Promise&amp;lt;LLMResponse&amp;gt; {
    const providerName = options.provider || this.defaultProvider;
    const primaryProvider = this.providers.get(providerName);

    try {
      return await this.executeWithRetry(primaryProvider, messages, options);
    } catch (error) {
      // 페일오버 로직
      for (const fallbackName of this.fallbackProviders) {
        if (fallbackName === providerName) continue;

        const fallbackProvider = this.providers.get(fallbackName);
        if (!fallbackProvider) continue;

        try {
          const isAvailable = await fallbackProvider.isAvailable();
          if (!isAvailable) continue;

          return await this.executeWithRetry(fallbackProvider, messages, options);
        } catch (fallbackError) {
          continue;
        }
      }
      throw error;
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이러한 설계로 OpenAI API가 장애를 겪어도 Claude나 Ollama로 자동 전환되어 서비스 연속성을 보장할 수 있었다.&lt;/p&gt;
&lt;h2&gt;3장: 실제 서비스 구현 - 이론에서 실전으로&lt;/h2&gt;
&lt;h3&gt;Chat Service: 실시간 대화의 구현&lt;/h3&gt;
&lt;p&gt;첫 번째로 구현한 서비스는 Chat Service였다. 이는 사용자가 AI와 실시간으로 대화할 수 있는 플랫폼이었다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// apps/services/chat-service/src/app/app.module.ts
@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: [&amp;#39;.env.local&amp;#39;, &amp;#39;.env&amp;#39;],
    }),
    DatabaseModule.forRoot(),
    AuthModule,
    LLMModule.forRoot({
      defaultProvider: &amp;#39;openai&amp;#39;,
      fallbackProviders: [&amp;#39;claude&amp;#39;, &amp;#39;ollama&amp;#39;],
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;코어 라이브러리들을 조합하여 서비스를 구성하는 과정에서 모노레포의 진가를 확인할 수 있었다. 각 라이브러리가 독립적으로 동작하면서도 seamless하게 통합되었다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// apps/services/chat-service/src/app/app.controller.ts
@Controller(&amp;#39;chat&amp;#39;)
export class ChatController {
  constructor(
    private readonly llmService: LLMService,
  ) {}

  @Post(&amp;#39;message&amp;#39;)
  @UseGuards(JwtAuthGuard)
  async sendMessage(
    @CurrentUser() user: any,
    @Body() body: { message: string; conversationId?: string }
  ) {
    const messages = [
      { role: &amp;#39;system&amp;#39;, content: &amp;#39;당신은 도움이 되는 AI 어시스턴트입니다.&amp;#39; },
      { role: &amp;#39;user&amp;#39;, content: body.message },
    ];

    try {
      const response = await this.llmService.generateCompletion(messages, {
        temperature: 0.7,
        maxTokens: 1000,
      });

      return {
        success: true,
        data: {
          response: response.content,
          provider: response.provider,
          usage: response.usage,
        },
      };
    } catch (error) {
      return {
        success: false,
        message: &amp;#39;응답 생성 중 오류가 발생했습니다.&amp;#39;,
        error: error.message,
      };
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;서비스 생성 자동화&lt;/h3&gt;
&lt;p&gt;여러 서비스를 생성하면서 반복 작업이 늘어나자, 자동화 스크립트를 만들었다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// tools/scripts/create-service.js
const { execSync } = require(&amp;#39;child_process&amp;#39;);
const fs = require(&amp;#39;fs&amp;#39;);
const path = require(&amp;#39;path&amp;#39;);

function createService(serviceName) {
  console.log(`Creating service: ${serviceName}`);

  // Nx 애플리케이션 생성
  execSync(`npx nx g @nx/nest:app ${serviceName} --directory=apps/services`, 
    { stdio: &amp;#39;inherit&amp;#39; });

  // E2E 테스트 애플리케이션 생성  
  execSync(`npx nx g @nx/nest:app ${serviceName}-e2e --directory=apps/services`, 
    { stdio: &amp;#39;inherit&amp;#39; });

  // 프로젝트 설정 업데이트
  updateProjectConfig(serviceName);

  // 기본 템플릿 적용
  applyTemplate(serviceName);

  console.log(`Service ${serviceName} created successfully!`);
  console.log(`To start the service: nx serve ${serviceName}`);
  console.log(`To build the service: nx build ${serviceName}`);
  console.log(`To run tests: nx test ${serviceName}`);
}

function updateProjectConfig(serviceName) {
  const projectPath = `apps/services/${serviceName}/project.json`;
  const config = JSON.parse(fs.readFileSync(projectPath, &amp;#39;utf8&amp;#39;));

  // 추가 태그 설정
  config.tags = [...(config.tags || []), &amp;#39;scope:services&amp;#39;, &amp;#39;type:app&amp;#39;];

  // 환경 변수 설정
  config.targets.serve.options.envFile = &amp;#39;.env.local&amp;#39;;

  fs.writeFileSync(projectPath, JSON.stringify(config, null, 2));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 스크립트를 통해 새로운 서비스를 5분 안에 생성하고 바로 개발을 시작할 수 있었다.&lt;/p&gt;
&lt;h2&gt;4장: 개발 과정에서의 도전과 해결&lt;/h2&gt;
&lt;h3&gt;타입 시스템과의 싸움&lt;/h3&gt;
&lt;p&gt;TypeScript 설정을 맞추는 과정에서 여러 문제에 부딪혔다. 특히 모노레포 환경에서 라이브러리 간 타입 참조가 제대로 작동하지 않는 문제가 있었다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;// tsconfig.base.json
{
  &amp;quot;compileOnSave&amp;quot;: false,
  &amp;quot;compilerOptions&amp;quot;: {
    &amp;quot;rootDir&amp;quot;: &amp;quot;.&amp;quot;,
    &amp;quot;sourceMap&amp;quot;: true,
    &amp;quot;declaration&amp;quot;: false,
    &amp;quot;moduleResolution&amp;quot;: &amp;quot;node&amp;quot;,
    &amp;quot;emitDecoratorMetadata&amp;quot;: true,
    &amp;quot;experimentalDecorators&amp;quot;: true,
    &amp;quot;importHelpers&amp;quot;: true,
    &amp;quot;target&amp;quot;: &amp;quot;es2015&amp;quot;,
    &amp;quot;module&amp;quot;: &amp;quot;esnext&amp;quot;,
    &amp;quot;lib&amp;quot;: [&amp;quot;es2020&amp;quot;, &amp;quot;dom&amp;quot;],
    &amp;quot;skipLibCheck&amp;quot;: true,
    &amp;quot;skipDefaultLibCheck&amp;quot;: true,
    &amp;quot;baseUrl&amp;quot;: &amp;quot;.&amp;quot;,
    &amp;quot;paths&amp;quot;: {
      &amp;quot;@ai-solution/core/auth&amp;quot;: [&amp;quot;libs/core/auth/src/index.ts&amp;quot;],
      &amp;quot;@ai-solution/core/database&amp;quot;: [&amp;quot;libs/core/database/src/index.ts&amp;quot;],
      &amp;quot;@ai-solution/core/llm&amp;quot;: [&amp;quot;libs/core/llm/src/index.ts&amp;quot;]
    }
  },
  &amp;quot;exclude&amp;quot;: [&amp;quot;node_modules&amp;quot;, &amp;quot;tmp&amp;quot;]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;경로 매핑을 통해 절대 경로 import를 가능하게 했지만, 빌드 시점에서 문제가 발생했다. 이를 해결하기 위해 각 라이브러리의 tsconfig.lib.json을 세심하게 조정했다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;// libs/core/auth/tsconfig.lib.json
{
  &amp;quot;extends&amp;quot;: &amp;quot;../../../tsconfig.base.json&amp;quot;,
  &amp;quot;compilerOptions&amp;quot;: {
    &amp;quot;module&amp;quot;: &amp;quot;commonjs&amp;quot;,
    &amp;quot;forceConsistentCasingInFileNames&amp;quot;: true,
    &amp;quot;strict&amp;quot;: true,
    &amp;quot;noImplicitOverride&amp;quot;: true,
    &amp;quot;noPropertyAccessFromIndexSignature&amp;quot;: true,
    &amp;quot;noImplicitReturns&amp;quot;: true,
    &amp;quot;noFallthroughCasesInSwitch&amp;quot;: true,
    &amp;quot;declaration&amp;quot;: true,
    &amp;quot;outDir&amp;quot;: &amp;quot;../../../dist/libs/core/auth&amp;quot;
  },
  &amp;quot;include&amp;quot;: [&amp;quot;src/**/*&amp;quot;],
  &amp;quot;exclude&amp;quot;: [&amp;quot;jest.config.ts&amp;quot;, &amp;quot;src/**/*.spec.ts&amp;quot;, &amp;quot;src/**/*.test.ts&amp;quot;]
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;빌드 의존성 관리&lt;/h3&gt;
&lt;p&gt;모노레포의 장점이자 단점은 의존성 관리였다. core-auth가 변경되면 이를 사용하는 모든 서비스가 재빌드되어야 했다. Nx의 의존성 그래프를 활용하여 이를 최적화했다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 의존성 그래프 시각화
nx graph

# 영향받는 프로젝트만 빌드
nx affected:build --base=main --head=HEAD

# 특정 라이브러리 변경 시 영향 분석
nx affected:graph --base=main&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODAwIiBoZWlnaHQ9IjYwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8IS0tIE54IERlcGVuZGVuY3kgR3JhcGggUmVwcmVzZW50YXRpb24gLS0+CiAgPGRlZnM+CiAgICA8bWFya2VyIGlkPSJhcnJvd2hlYWQiIG1hcmtlcldpZHRoPSIxMCIgbWFya2VySGVpZ2h0PSI3IiByZWZYPSI5IiByZWZZPSIzLjUiIG9yaWVudD0iYXV0byI+CiAgICAgIDxwb2x5Z29uIHBvaW50cz0iMCAwLCAxMCAzLjUsIDAgNyIgZmlsbD0iIzMzNzNkYyIgLz4KICAgIDwvbWFya2VyPgogIDwvZGVmcz4KICA8IS0tIENvcmUgTGlicmFyaWVzIC0tPgogIDxyZWN0IHg9IjUwIiB5PSI1MCIgd2lkdGg9IjEyMCIgaGVpZ2h0PSI2MCIgcng9IjUiIGZpbGw9IiNmZmY5ZmYiIHN0cm9rZT0iIzMzNzNkYyIgc3Ryb2tlLXdpZHRoPSIyIi8+CiAgPHRleHQgeD0iMTEwIiB5PSI4NSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC1mYW1pbHk9IkFyaWFsIiBmb250LXNpemU9IjEyIiBmb250LXdlaWdodD0iYm9sZCI+Y29yZS1hdXRoPC90ZXh0PgogIDxyZWN0IHg9IjUwIiB5PSIxNTAiIHdpZHRoPSIxMjAiIGhlaWdodD0iNjAiIHJ4PSI1IiBmaWxsPSIjZmZmOWZmIiBzdHJva2U9IiMzMzczZGMiIHN0cm9rZS13aWR0aD0iMiIvPgogIDx0ZXh0IHg9IjExMCIgeT0iMTg1IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTIiIGZvbnQtd2VpZ2h0PSJib2xkIj5jb3JlLWRhdGFiYXNlPC90ZXh0PgogIDxyZWN0IHg9IjUwIiB5PSIyNTAiIHdpZHRoPSIxMjAiIGhlaWdodD0iNjAiIHJ4PSI1IiBmaWxsPSIjZmZmOWZmIiBzdHJva2U9IiMzMzczZGMiIHN0cm9rZS13aWR0aD0iMiIvPgogIDx0ZXh0IHg9IjExMCIgeT0iMjg1IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTIiIGZvbnQtd2VpZ2h0PSJib2xkIj5jb3JlLWxsbTwvdGV4dD4KICA8IS0tIFNlcnZpY2VzIC0tPgogIDxyZWN0IHg9IjMwMCIgeT0iNTAiIHdpZHRoPSIxMjAiIGhlaWdodD0iNjAiIHJ4PSI1IiBmaWxsPSIjZmZmNGVkIiBzdHJva2U9IiNmOTc0MTYiIHN0cm9rZS13aWR0aD0iMiIvPgogIDx0ZXh0IHg9IjM2MCIgeT0iODUiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgZm9udC13ZWlnaHQ9ImJvbGQiPmNoYXQtc2VydmljZTwvdGV4dD4KICA8cmVjdCB4PSIzMDAiIHk9IjE1MCIgd2lkdGg9IjEyMCIgaGVpZ2h0PSI2MCIgcng9IjUiIGZpbGw9IiNmZmY0ZWQiIHN0cm9rZT0iI2Y5NzQxNiIgc3Ryb2tlLXdpZHRoPSIyIi8+CiAgPHRleHQgeD0iMzYwIiB5PSIxODUiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgZm9udC13ZWlnaHQ9ImJvbGQiPmFuYWx5dGljcy1zZXJ2aWNlPC90ZXh0PgogIDxyZWN0IHg9IjMwMCIgeT0iMjUwIiB3aWR0aD0iMTIwIiBoZWlnaHQ9IjYwIiByeD0iNSIgZmlsbD0iI2ZmZjRlZCIgc3Ryb2tlPSIjZjk3NDE2IiBzdHJva2Utd2lkdGg9IjIiLz4KICA8dGV4dCB4PSIzNjAiIHk9IjI4NSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC1mYW1pbHk9IkFyaWFsIiBmb250LXNpemU9IjEyIiBmb250LXdlaWdodD0iYm9sZCI+dXNlci1zZXJ2aWNlPC90ZXh0PgogIDwhLS0gRGVwZW5kZW5jeSBBcnJvd3MgLS0+CiAgPGxpbmUgeDE9IjE3MCIgeTE9IjgwIiB4Mj0iMzAwIiB5Mj0iODAiIHN0cm9rZT0iIzMzNzNkYyIgc3Ryb2tlLXdpZHRoPSIyIiBtYXJrZXItZW5kPSJ1cmwoI2Fycm93aGVhZCkiLz4KICA8bGluZSB4MT0iMTcwIiB5MT0iMTgwIiB4Mj0iMzAwIiB5Mj0iMTgwIiBzdHJva2U9IiMzMzczZGMiIHN0cm9rZS13aWR0aD0iMiIgbWFya2VyLWVuZD0idXJsKCNhcnJvd2hlYWQpIi8+CiAgPGxpbmUgeDE9IjE3MCIgeTE9IjI4MCIgeDI9IjMwMCIgeTI9IjI4MCIgc3Ryb2tlPSIjMzM3M2RjIiBzdHJva2Utd2lkdGg9IjIiIG1hcmtlci1lbmQ9InVybCgjYXJyb3doZWFkKSIvPgogIDxsaW5lIHgxPSIxNzAiIHkxPSI4MCIgeDI9IjMwMCIgeTI9IjE4MCIgc3Ryb2tlPSIjMzM3M2RjIiBzdHJva2Utd2lkdGg9IjIiIG1hcmtlci1lbmQ9InVybCgjYXJyb3doZWFkKSIvPgogIDxsaW5lIHgxPSIxNzAiIHkxPSI4MCIgeDI9IjMwMCIgeTI9IjI4MCIgc3Ryb2tlPSIjMzM3M2RjIiBzdHJva2Utd2lkdGg9IjIiIG1hcmtlci1lbmQ9InVybCgjYXJyb3doZWFkKSIvPgogIDxsaW5lIHgxPSIxNzAiIHkxPSIxODAiIHgyPSIzMDAiIHkyPSI4MCIgc3Ryb2tlPSIjMzM3M2RjIiBzdHJva2Utd2lkdGg9IjIiIG1hcmtlci1lbmQ9InVybCgjYXJyb3doZWFkKSIvPgogIDxsaW5lIHgxPSIxNzAiIHkxPSIxODAiIHgyPSIzMDAiIHkyPSIyODAiIHN0cm9rZT0iIzMzNzNkYyIgc3Ryb2tlLXdpZHRoPSIyIiBtYXJrZXItZW5kPSJ1cmwoI2Fycm93aGVhZCkiLz4KICA8IS0tIExlZ2VuZCAtLT4KICA8dGV4dCB4PSI1MDAiIHk9IjMwIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTQiIGZvbnQtd2VpZ2h0PSJib2xkIj5EZXBlbmRlbmN5IEdyYXBoPC90ZXh0PgogIDxyZWN0IHg9IjUwMCIgeT0iNTAiIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgZmlsbD0iI2ZmZjlmZiIgc3Ryb2tlPSIjMzM3M2RjIiBzdHJva2Utd2lkdGg9IjIiLz4KICA8dGV4dCB4PSI1MzAiIHk9IjY1IiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTIiPkNvcmUgTGlicmFyaWVzPC90ZXh0PgogIDxyZWN0IHg9IjUwMCIgeT0iODAiIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgZmlsbD0iI2ZmZjRlZCIgc3Ryb2tlPSIjZjk3NDE2IiBzdHJva2Utd2lkdGg9IjIiLz4KICA8dGV4dCB4PSI1MzAiIHk9Ijk1IiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTIiPlNlcnZpY2VzPC90ZXh0Pgo8L3N2Zz4K&quot; alt=&quot;Nx Dependency Graph&quot;&gt;&lt;/p&gt;
&lt;h3&gt;환경 변수 관리의 복잡성&lt;/h3&gt;
&lt;p&gt;여러 서비스가 서로 다른 환경 변수를 필요로 하면서 관리가 복잡해졌다. 이를 해결하기 위해 계층적 환경 변수 구조를 도입했다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 프로젝트 루트
.env                    # 공통 환경 변수
.env.local             # 로컬 개발용 (gitignore)
.env.production        # 프로덕션용 (gitignore)

# 서비스별 환경 변수
apps/services/chat-service/.env
apps/services/analytics-service/.env&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// apps/services/chat-service/src/main.ts
import { ConfigService } from &amp;#39;@nestjs/config&amp;#39;;

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  const configService = app.get(ConfigService);

  // 환경별 설정
  const port = configService.get&amp;lt;number&amp;gt;(&amp;#39;PORT&amp;#39;, 3000);
  const environment = configService.get&amp;lt;string&amp;gt;(&amp;#39;NODE_ENV&amp;#39;, &amp;#39;development&amp;#39;);

  if (environment === &amp;#39;production&amp;#39;) {
    app.enableCors({
      origin: configService.get&amp;lt;string&amp;gt;(&amp;#39;ALLOWED_ORIGINS&amp;#39;, &amp;#39;&amp;#39;).split(&amp;#39;,&amp;#39;),
      credentials: true,
    });
  } else {
    app.enableCors();
  }

  await app.listen(port);
  console.log(`Application is running on: ${await app.getUrl()}`);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;5장: 성능 최적화와 모니터링&lt;/h2&gt;
&lt;h3&gt;빌드 시간 최적화&lt;/h3&gt;
&lt;p&gt;초기에는 전체 프로젝트를 빌드하는데 5분 이상이 걸렸다. 이는 개발 생산성에 심각한 영향을 미쳤다. Nx의 캐싱 시스템을 활용하여 이를 개선했다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;// nx.json - 캐싱 설정
{
  &amp;quot;tasksRunnerOptions&amp;quot;: {
    &amp;quot;default&amp;quot;: {
      &amp;quot;runner&amp;quot;: &amp;quot;@nx/workspace/tasks-runners/default&amp;quot;,
      &amp;quot;options&amp;quot;: {
        &amp;quot;cacheableOperations&amp;quot;: [&amp;quot;build&amp;quot;, &amp;quot;lint&amp;quot;, &amp;quot;test&amp;quot;, &amp;quot;e2e&amp;quot;],
        &amp;quot;parallel&amp;quot;: 3,
        &amp;quot;useDaemonProcess&amp;quot;: true
      }
    }
  },
  &amp;quot;targetDefaults&amp;quot;: {
    &amp;quot;build&amp;quot;: {
      &amp;quot;dependsOn&amp;quot;: [&amp;quot;^build&amp;quot;],
      &amp;quot;inputs&amp;quot;: [&amp;quot;production&amp;quot;, &amp;quot;^production&amp;quot;],
      &amp;quot;cache&amp;quot;: true
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;결과적으로 증분 빌드 시간을 30초 이내로 단축시킬 수 있었다.&lt;/p&gt;
&lt;h3&gt;메모리 사용량 모니터링&lt;/h3&gt;
&lt;p&gt;대용량 AI 모델을 다루면서 메모리 사용량이 중요한 이슈가 되었다. 각 서비스별로 메모리 모니터링을 구현했다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// 메모리 사용량 모니터링 미들웨어
@Injectable()
export class MemoryMonitoringMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    const memoryUsage = process.memoryUsage();
    const memoryInMB = {
      rss: Math.round(memoryUsage.rss / 1024 / 1024),
      heapTotal: Math.round(memoryUsage.heapTotal / 1024 / 1024),
      heapUsed: Math.round(memoryUsage.heapUsed / 1024 / 1024),
      external: Math.round(memoryUsage.external / 1024 / 1024),
    };

    if (memoryInMB.heapUsed &amp;gt; 512) { // 512MB 임계치
      console.warn(`High memory usage detected: ${JSON.stringify(memoryInMB)}`);
    }

    res.setHeader(&amp;#39;X-Memory-Usage&amp;#39;, JSON.stringify(memoryInMB));
    next();
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;6장: 문서화와 개발 경험&lt;/h2&gt;
&lt;h3&gt;API 문서화의 중요성&lt;/h3&gt;
&lt;p&gt;팀 규모가 커지면서 API 문서화의 중요성을 절감했다. Swagger를 도입하여 자동화된 API 문서를 구축했다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// main.ts에서 Swagger 설정
import { SwaggerModule, DocumentBuilder } from &amp;#39;@nestjs/swagger&amp;#39;;

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const config = new DocumentBuilder()
    .setTitle(&amp;#39;AI Solution Center API&amp;#39;)
    .setDescription(&amp;#39;AI 솔루션센터 통합 API 문서&amp;#39;)
    .setVersion(&amp;#39;1.0&amp;#39;)
    .addBearerAuth()
    .addTag(&amp;#39;auth&amp;#39;, &amp;#39;인증 관련 API&amp;#39;)
    .addTag(&amp;#39;chat&amp;#39;, &amp;#39;채팅 관련 API&amp;#39;)
    .addTag(&amp;#39;analytics&amp;#39;, &amp;#39;분석 관련 API&amp;#39;)
    .build();

  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup(&amp;#39;api-docs&amp;#39;, app, document);

  await app.listen(3000);
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// DTO에 Swagger 데코레이터 적용
export class ChatMessageDto {
  @ApiProperty({
    description: &amp;#39;사용자 메시지&amp;#39;,
    example: &amp;#39;안녕하세요, AI 어시스턴트입니다.&amp;#39;,
  })
  @IsString()
  @IsNotEmpty()
  message: string;

  @ApiProperty({
    description: &amp;#39;대화 ID (선택사항)&amp;#39;,
    example: &amp;#39;conv_123456789&amp;#39;,
    required: false,
  })
  @IsOptional()
  @IsString()
  conversationId?: string;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;개발자 경험 개선&lt;/h3&gt;
&lt;p&gt;개발자 경험을 개선하기 위해 여러 도구를 도입했다:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 코드 품질 도구&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;// .eslintrc.json
{
  &amp;quot;extends&amp;quot;: [&amp;quot;@nx/eslint-plugin-nx/typescript&amp;quot;],
  &amp;quot;rules&amp;quot;: {
    &amp;quot;@typescript-eslint/no-unused-vars&amp;quot;: &amp;quot;error&amp;quot;,
    &amp;quot;@typescript-eslint/explicit-function-return-type&amp;quot;: &amp;quot;warn&amp;quot;,
    &amp;quot;prefer-const&amp;quot;: &amp;quot;error&amp;quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;2. 프리커밋 훅&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;// package.json
{
  &amp;quot;husky&amp;quot;: {
    &amp;quot;hooks&amp;quot;: {
      &amp;quot;pre-commit&amp;quot;: &amp;quot;lint-staged&amp;quot;,
      &amp;quot;commit-msg&amp;quot;: &amp;quot;commitlint -E HUSKY_GIT_PARAMS&amp;quot;
    }
  },
  &amp;quot;lint-staged&amp;quot;: {
    &amp;quot;*.{ts,tsx}&amp;quot;: [
      &amp;quot;eslint --fix&amp;quot;,
      &amp;quot;prettier --write&amp;quot;
    ]
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;3. 커밋 메시지 규칙&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// commitlint.config.js
module.exports = {
  extends: [&amp;#39;@commitlint/config-conventional&amp;#39;],
  rules: {
    &amp;#39;type-enum&amp;#39;: [
      2,
      &amp;#39;always&amp;#39;,
      [&amp;#39;feat&amp;#39;, &amp;#39;fix&amp;#39;, &amp;#39;docs&amp;#39;, &amp;#39;style&amp;#39;, &amp;#39;refactor&amp;#39;, &amp;#39;test&amp;#39;, &amp;#39;chore&amp;#39;]
    ],
    &amp;#39;subject-max-length&amp;#39;: [2, &amp;#39;always&amp;#39;, 100],
  },
};&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;7장: 배포와 운영&lt;/h2&gt;
&lt;h3&gt;컨테이너화 전략&lt;/h3&gt;
&lt;p&gt;각 서비스를 독립적으로 배포하기 위해 Docker를 활용했다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;# apps/services/chat-service/Dockerfile
FROM node:18-alpine AS builder

WORKDIR /app
COPY package*.json ./
COPY yarn.lock ./
RUN yarn install --frozen-lockfile

COPY . .
RUN yarn nx build chat-service --prod

FROM node:18-alpine AS runner

WORKDIR /app
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nestjs -u 1001

COPY --from=builder /app/dist/apps/services/chat-service ./
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json

USER nestjs
EXPOSE 3000

CMD [&amp;quot;node&amp;quot;, &amp;quot;main.js&amp;quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;CI/CD 파이프라인&lt;/h3&gt;
&lt;p&gt;GitHub Actions을 사용하여 자동화된 배포 파이프라인을 구축했다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# .github/workflows/ci.yml
name: CI/CD Pipeline

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: &amp;#39;18&amp;#39;
          cache: &amp;#39;yarn&amp;#39;

      - name: Install dependencies
        run: yarn install --frozen-lockfile

      - name: Run affected tests
        run: yarn nx affected:test --base=origin/main

      - name: Run affected lint
        run: yarn nx affected:lint --base=origin/main

      - name: Run affected build
        run: yarn nx affected:build --base=origin/main

  deploy:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == &amp;#39;refs/heads/main&amp;#39;
    steps:
      - uses: actions/checkout@v3

      - name: Deploy to staging
        run: |
          # 스테이징 환경 배포 스크립트
          ./scripts/deploy-staging.sh&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;8장: 성과와 교훈&lt;/h2&gt;
&lt;h3&gt;정량적 성과&lt;/h3&gt;
&lt;p&gt;6개월간의 개발 과정에서 다음과 같은 성과를 달성했다:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;프로젝트 메트릭스:
├── 서비스 수: 7개
├── 코어 라이브러리: 3개
├── 코드 재사용률: 73%
├── 빌드 시간 단축: 70%
├── 버그 감소율: 45%
└── 개발 생산성 향상: 60%&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;기술적 성과&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;모듈화와 재사용성&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// 코드 재사용 예시
// 모든 서비스에서 동일한 인증 로직 사용
@UseGuards(JwtAuthGuard)
@Get(&amp;#39;protected&amp;#39;)
protectedEndpoint(@CurrentUser() user: User) {
  return { user };
}

// 모든 엔티티에서 동일한 기본 필드 사용
@Entity()
export class CustomEntity extends BaseEntity {
  // 추가 필드만 정의
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;개발 속도 향상&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 새 서비스 생성: 5분
npm run create-service new-service

# 기능 추가: 기존 대비 50% 시간 단축
# 배포: 자동화로 95% 시간 단축&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;아키텍처적 통찰&lt;/h3&gt;
&lt;p&gt;이 프로젝트를 통해 얻은 가장 중요한 통찰은 &lt;strong&gt;&amp;quot;복잡성은 관리되어야 한다&amp;quot;&lt;/strong&gt;는 것이었다. &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;복잡성 관리 전략:
├── 계층화: 비즈니스 로직과 인프라 분리
├── 추상화: 공통 기능의 라이브러리화
├── 표준화: 코딩 스타일과 패턴 통일
├── 자동화: 반복 작업의 스크립트화
└── 문서화: 지식의 체계적 관리&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;처음에는 모든 것을 한 번에 완벽하게 만들려고 했지만, 점진적 개선이 더 효과적임을 깨달았다. 특히 코어 라이브러리는 실제 사용 사례가 누적된 후에 안정화되었다.&lt;/p&gt;
&lt;h3&gt;팀워크와 협업&lt;/h3&gt;
&lt;p&gt;혼자 시작한 프로젝트였지만, 점차 팀이 확장되면서 협업의 중요성을 체감했다. 특히 다음 요소들이 핵심이었다:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 코드 리뷰 문화&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// 리뷰 가이드라인
// 1. 비즈니스 로직이 명확한가?
// 2. 에러 처리가 적절한가?
// 3. 테스트 커버리지가 충분한가?
// 4. 성능상 이슈는 없는가?&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;2. 지식 공유&lt;/strong&gt;&lt;br&gt;매주 금요일 기술 세션을 통해 새로운 기술이나 문제 해결 과정을 공유했다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 페어 프로그래밍&lt;/strong&gt;&lt;br&gt;복잡한 문제는 페어 프로그래밍을 통해 해결했고, 이는 코드 품질 향상에 큰 도움이 되었다.&lt;/p&gt;
&lt;h2&gt;에필로그: 미래를 향한 설계&lt;/h2&gt;
&lt;p&gt;현재 구축한 플랫폼은 시작점일 뿐이다. 앞으로 마이크로서비스 아키텍처로의 전환, 쿠버네티스 기반 오케스트레이션, 이벤트 기반 아키텍처 도입 등을 고려하고 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;미래 로드맵:
├── Q1: 마이크로서비스 전환 준비
│   ├── 서비스 메시 도입 (Istio)
│   ├── 분산 트레이싱 (Jaeger)
│   └── 중앙집중식 로깅 (ELK Stack)
├── Q2: 클라우드 네이티브 전환
│   ├── Kubernetes 클러스터 구축
│   ├── GitOps 파이프라인 (ArgoCD)
│   └── 인프라스트럭처 as 코드 (Terraform)
├── Q3: AI/ML 파이프라인 강화
│   ├── MLOps 플랫폼 구축
│   ├── 모델 버저닝 시스템
│   └── A/B 테스트 프레임워크
└── Q4: 성능 및 확장성 최적화
    ├── 캐싱 전략 고도화 (Redis Cluster)
    ├── 데이터베이스 샤딩
    └── CDN 및 엣지 컴퓨팅&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;이 프로젝트를 통해 배운 가장 중요한 교훈은 기술은 수단이지 목적이 아니라는 것이다. 사용자의 문제를 해결하는 것이 우선이고, 기술적 선택은 그 목적을 달성하기 위한 최적의 방법이어야 한다.&lt;/p&gt;
&lt;p&gt;AI혁신 솔루션센터는 이제 실제 사용자들에게 가치를 제공하는 플랫폼으로 성장하고 있다. 앞으로도 지속적인 개선과 혁신을 통해 더 나은 AI 솔루션을 제공할 것이다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;*&amp;quot;좋은 아키텍처는 하루 아침에 만들어지지 않는다. 지속적인 개선과 학습을 통해 진화하는 것이다.&amp;quot;*&lt;/p&gt;</description>
      <author>글쓰는 연구원 상티</author>
      <guid isPermaLink="true">https://researchwithme.tistory.com/14</guid>
      <comments>https://researchwithme.tistory.com/14#entry14comment</comments>
      <pubDate>Sat, 16 Aug 2025 17:13:36 +0900</pubDate>
    </item>
    <item>
      <title>Unitree SDK 분석해보기</title>
      <link>https://researchwithme.tistory.com/13</link>
      <description>&lt;h2&gt;목차&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;#%EA%B0%9C%EC%9A%94&quot;&gt;개요&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98&quot;&gt;시스템 아키텍처&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#%EB%B3%B4%ED%96%89-%EC%A0%9C%EC%96%B4-%EB%B0%A9%EC%8B%9D&quot;&gt;보행 제어 방식&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#%ED%95%B5%EC%8B%AC-api-%EB%B6%84%EC%84%9D&quot;&gt;핵심 API 분석&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#%EC%BD%94%EB%93%9C-%EC%8B%A4%ED%96%89-%ED%9D%90%EB%A6%84&quot;&gt;코드 실행 흐름&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#%EC%A3%BC%EC%9A%94-%EB%B3%B4%ED%96%89-%EA%B8%B0%EB%8A%A5&quot;&gt;주요 보행 기능&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#%EA%B2%B0%EB%A1%A0-%EB%B0%8F-%EC%8B%9C%EC%82%AC%EC%A0%90&quot;&gt;결론 및 시사점&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;개요&lt;/h2&gt;
&lt;p&gt;로보틱스 연구를 하다보면 특정 디바이스에 종속된 movement, locomotion, task를 수행해야 할 때가 있다. 최근들어 Unitree사의 Go2를 시뮬레이션상에서 연구 할 일이 많아 따로 작성해봄.&lt;/p&gt;
&lt;p&gt;Unitree SDK2 Python은 Unitree 로봇(Go2, B2, G1, H1 등)을 제어하기 위한 공식 Python API임. 이 글은 Go2 로봇의 보행 로직을 중심으로 API의 작동 원리와 구현 방식을 분석해봄.&lt;/p&gt;
&lt;h3&gt;주요 특징&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DDS(Data Distribution Service) 기반 통신&lt;/strong&gt;: 실시간 분산 통신 프로토콜 사용&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;계층적 제어 구조&lt;/strong&gt;: High-level(Sport Mode)과 Low-level 제어 분리&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RPC 기반 서비스 아키텍처&lt;/strong&gt;: 모듈화된 서비스 구조&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;시스템 아키텍처&lt;/h2&gt;
&lt;h3&gt;1. 디렉토리 구조&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;unitree_sdk2_python/
├── example/              # 예제 코드
│   └── go2/
│       ├── high_level/   # 고수준 제어 예제
│       └── low_level/    # 저수준 제어 예제
├── unitree_sdk2py/       # 핵심 라이브러리
│   ├── core/            # 통신 채널 및 설정
│   ├── go2/             # Go2 전용 모듈
│   │   ├── sport/       # Sport 모드 제어
│   │   └── robot_state/ # 로봇 상태 관리
│   ├── rpc/             # RPC 통신 모듈
│   └── idl/             # 데이터 타입 정의&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;2. 통신 아키텍처&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;┌─────────────────┐     RPC/DDS     ┌─────────────────┐
│  Python Client  │ ◄─────────────► │  Robot Service  │
│  (SDK)          │                  │  (내장 서비스)   │
└─────────────────┘                  └─────────────────┘
        │                                     │
        │                                     │
    ┌───▼───┐                           ┌────▼────┐
    │  High │                           │  Low    │
    │  Level│                           │  Level  │
    └───────┘                           └─────────┘&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;보행 제어 방식&lt;/h2&gt;
&lt;h3&gt;1. High-Level 제어 (Sport Mode)&lt;/h3&gt;
&lt;p&gt;고수준 제어는 SportClient를 통해 이루어지며, 복잡한 동작을 간단한 명령으로 실행할 수 있습니다.&lt;/p&gt;
&lt;h4&gt;주요 특징:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;추상화된 명령&lt;/strong&gt;: Move(vx, vy, vyaw) 같은 직관적인 인터페이스&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;자동 균형 제어&lt;/strong&gt;: 내부적으로 자세 제어 및 균형 유지&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;사전 정의된 동작&lt;/strong&gt;: 백플립, 춤, 계단 오르기 등&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. Low-Level 제어&lt;/h3&gt;
&lt;p&gt;저수준 제어는 개별 모터를 직접 제어하며, 정밀한 움직임 구현이 가능합니다.&lt;/p&gt;
&lt;h4&gt;주요 특징:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;모터 직접 제어&lt;/strong&gt;: 위치(q), 속도(dq), 토크(tau) 제어&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PD 제어기&lt;/strong&gt;: Kp(비례 게인), Kd(미분 게인) 설정&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;실시간 피드백&lt;/strong&gt;: 2ms 주기로 제어 명령 전송&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;핵심 API 분석&lt;/h2&gt;
&lt;h3&gt;1. SportClient 클래스 구조&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class SportClient(Client):
    def __init__(self, enableLease: bool = False):
        super().__init__(SPORT_SERVICE_NAME, enableLease)

    def Init(self):
        # API 버전 설정
        self._SetApiVerson(SPORT_API_VERSION)

        # API 등록 (40개 이상의 API)
        self._RegistApi(SPORT_API_ID_DAMP, 0)
        self._RegistApi(SPORT_API_ID_MOVE, 0)
        # ...&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 주요 보행 관련 메서드&lt;/h3&gt;
&lt;h4&gt;기본 이동 제어&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def Move(self, vx: float, vy: float, vyaw: float):
    &amp;quot;&amp;quot;&amp;quot;
    로봇 이동 제어
    - vx: 전진/후진 속도 (m/s)
    - vy: 좌우 이동 속도 (m/s)
    - vyaw: 회전 속도 (rad/s)
    &amp;quot;&amp;quot;&amp;quot;
    p = {&amp;quot;x&amp;quot;: vx, &amp;quot;y&amp;quot;: vy, &amp;quot;z&amp;quot;: vyaw}
    parameter = json.dumps(p)
    code = self._CallNoReply(SPORT_API_ID_MOVE, parameter)
    return code&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;보행 모드 전환&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def SwitchGait(self, t: int):
    &amp;quot;&amp;quot;&amp;quot;
    보행 패턴 변경
    - 0: 트로팅(Trotting) - 대각선 다리 동시 이동
    - 1: 기타 보행 패턴
    &amp;quot;&amp;quot;&amp;quot;
    p = {&amp;quot;data&amp;quot;: t}
    parameter = json.dumps(p)
    code, data = self._Call(SPORT_API_ID_SWITCHGAIT, parameter)
    return code&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;고급 보행 기능&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 자유 보행 모드
def FreeWalk(self, flag: bool)

# 장애물 회피 보행
def FreeAvoid(self, flag: bool)

# 계단 오르기
def WalkStair(self, flag: bool)

# 직립 보행
def WalkUpright(self, flag: bool)&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. Low-Level 모터 제어&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class MotorCmd_:
    mode: uint8      # 제어 모드 (0x01: PMSM 모드)
    q: float32       # 목표 위치 (라디안)
    dq: float32      # 목표 속도 (라디안/초)
    tau: float32     # 목표 토크 (Nm)
    kp: float32      # 위치 비례 게인
    kd: float32      # 속도 미분 게인&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;코드 실행 흐름&lt;/h2&gt;
&lt;h3&gt;1. High-Level 보행 예제 실행 흐름&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;1. 초기화
   ├─ ChannelFactoryInitialize() - DDS 통신 채널 초기화
   ├─ SportClient 생성
   └─ client.Init() - API 등록

2. 명령 실행 루프
   ├─ 사용자 입력 대기
   ├─ 명령 선택 (예: Move, StandUp, BackFlip 등)
   ├─ RPC 호출 (_Call 또는 _CallNoReply)
   └─ 로봇 서비스에서 명령 실행

3. 내부 처리 과정
   ├─ JSON 파라미터 생성
   ├─ API ID와 함께 서비스 호출
   ├─ 서비스에서 명령 해석
   └─ 모터 제어 신호 생성 및 전송&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;2. Low-Level 제어 실행 흐름&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;1. 초기화
   ├─ 통신 채널 설정
   ├─ Publisher/Subscriber 생성
   └─ 초기 모터 위치 읽기

2. 제어 루프 (2ms 주기)
   ├─ 현재 상태 읽기 (LowState)
   ├─ 목표 위치 계산
   ├─ PD 제어기 파라미터 설정
   ├─ CRC 계산
   └─ 명령 전송 (LowCmd)

3. 보간 및 전환
   ├─ 시작 위치에서 목표 위치로 부드러운 전환
   ├─ 각 단계별 진행률 계산 (percent_1, percent_2...)
   └─ 선형 보간을 통한 관절 각도 계산&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;주요 보행 기능&lt;/h2&gt;
&lt;h3&gt;1. 기본 보행 패턴&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;기능&lt;/th&gt;
&lt;th&gt;API&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;전진/후진&lt;/td&gt;
&lt;td&gt;Move(vx, 0, 0)&lt;/td&gt;
&lt;td&gt;직선 이동&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;좌우 이동&lt;/td&gt;
&lt;td&gt;Move(0, vy, 0)&lt;/td&gt;
&lt;td&gt;측면 이동 (크랩 워크)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;제자리 회전&lt;/td&gt;
&lt;td&gt;Move(0, 0, vyaw)&lt;/td&gt;
&lt;td&gt;Yaw 축 회전&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;트로팅&lt;/td&gt;
&lt;td&gt;SwitchGait(0)&lt;/td&gt;
&lt;td&gt;대각선 다리 동시 이동&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;2. 고급 보행 기능&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;기능&lt;/th&gt;
&lt;th&gt;API&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;자유 보행&lt;/td&gt;
&lt;td&gt;FreeWalk()&lt;/td&gt;
&lt;td&gt;AI 기반 적응형 보행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;장애물 회피&lt;/td&gt;
&lt;td&gt;FreeAvoid()&lt;/td&gt;
&lt;td&gt;센서 기반 자동 회피&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;계단 오르기&lt;/td&gt;
&lt;td&gt;WalkStair()&lt;/td&gt;
&lt;td&gt;계단 감지 및 적응&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;직립 보행&lt;/td&gt;
&lt;td&gt;WalkUpright()&lt;/td&gt;
&lt;td&gt;두 다리로 걷기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;교차 보행&lt;/td&gt;
&lt;td&gt;CrossStep()&lt;/td&gt;
&lt;td&gt;다리 교차 패턴&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;3. 특수 동작&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;동작&lt;/th&gt;
&lt;th&gt;API&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;백플립&lt;/td&gt;
&lt;td&gt;BackFlip()&lt;/td&gt;
&lt;td&gt;후방 공중제비&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;좌측 플립&lt;/td&gt;
&lt;td&gt;LeftFlip()&lt;/td&gt;
&lt;td&gt;측면 공중제비&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;춤&lt;/td&gt;
&lt;td&gt;Dance1(), Dance2()&lt;/td&gt;
&lt;td&gt;사전 정의된 춤 동작&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;점프&lt;/td&gt;
&lt;td&gt;FreeJump()&lt;/td&gt;
&lt;td&gt;자유 점프 동작&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;결론 및 시사점&lt;/h2&gt;
&lt;h3&gt;1. 강화학습 적용을 위한 고려사항&lt;/h3&gt;
&lt;h4&gt;장점:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;High-Level API&lt;/strong&gt;: 복잡한 동작을 간단히 실행 가능&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Low-Level 접근&lt;/strong&gt;: 정밀한 모터 제어로 커스텀 보행 패턴 구현 가능&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;실시간 피드백&lt;/strong&gt;: 2ms 주기의 빠른 제어 루프&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;도전 과제:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;제한된 중간 수준 제어&lt;/strong&gt;: High-Level과 Low-Level 사이의 갭&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;내부 알고리즘 불투명성&lt;/strong&gt;: Sport 모드의 내부 구현 비공개&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;센서 데이터 접근&lt;/strong&gt;: IMU, 발 힘 센서 등의 제한적 접근&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 권장 접근 방법&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;초기 단계&lt;/strong&gt;: High-Level API로 기본 동작 학습&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;중급 단계&lt;/strong&gt;: TrajectoryFollow API로 경로 기반 보행 구현&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;고급 단계&lt;/strong&gt;: Low-Level 제어로 커스텀 보행 패턴 개발&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;3. 주요 인사이트&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;계층적 제어의 중요성&lt;/strong&gt;: 추상화 수준에 따른 적절한 API 선택&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;실시간성 보장&lt;/strong&gt;: DDS 통신으로 낮은 지연시간 달성&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;모듈화된 구조&lt;/strong&gt;: 각 기능이 독립적인 서비스로 구현&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>글쓰는 연구원 상티</author>
      <guid isPermaLink="true">https://researchwithme.tistory.com/13</guid>
      <comments>https://researchwithme.tistory.com/13#entry13comment</comments>
      <pubDate>Mon, 30 Jun 2025 17:21:16 +0900</pubDate>
    </item>
    <item>
      <title>[부담없는 강화학습 이론]: On-Policy, Off-Policy</title>
      <link>https://researchwithme.tistory.com/12</link>
      <description>&lt;h2&gt;1. 핵심 개념 정립&lt;/h2&gt;
&lt;h3&gt;1.1 Policy의 본질적 이해&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Policy π(a|s)&lt;/strong&gt;: 상태 s에서 행동 a를 선택할 확률분포임&lt;/li&gt;
&lt;li&gt;요리사가 재료(상태)를 보고 어떤 요리법(행동)을 쓸지 결정하는 확률적 판단 기준임&lt;/li&gt;
&lt;li&gt;수학적으로 π: S → P(A)의 매핑 함수임&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.2 On-Policy vs Off-Policy의 철학적 차이&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;On-Policy&lt;/strong&gt;: &amp;quot;배우는 정책 = 행동하는 정책&amp;quot;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;현재 학습 중인 정책으로 직접 경험을 수집하고 그 경험으로만 학습함&lt;/li&gt;
&lt;li&gt;학생이 자신의 공부법으로만 공부하고 그 결과로만 공부법을 개선하는 방식임&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Off-Policy&lt;/strong&gt;: &amp;quot;배우는 정책 ≠ 행동하는 정책&amp;quot; &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;다른 정책(behavior policy)으로 수집한 경험으로 목표 정책(target policy)을 학습함&lt;/li&gt;
&lt;li&gt;다른 학생들의 공부 경험을 관찰하며 자신만의 최적 공부법을 찾는 방식임&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2. On-Policy 방법론 심화 분석&lt;/h2&gt;
&lt;h3&gt;2.1 SARSA (State-Action-Reward-State-Action)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;핵심 업데이트 공식:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Q(s,a) ← Q(s,a) + α[r + γQ(s&amp;#39;,a&amp;#39;) - Q(s,a)]&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;수식 해부:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Q(s,a)&lt;/code&gt;: 현재 상태-행동 가치 추정치&lt;/li&gt;
&lt;li&gt;&lt;code&gt;α&lt;/code&gt;: 학습률 (새로운 정보를 얼마나 반영할지)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;r&lt;/code&gt;: 즉시 보상&lt;/li&gt;
&lt;li&gt;&lt;code&gt;γ&lt;/code&gt;: 할인 인자 (미래 보상의 중요도)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Q(s&amp;#39;,a&amp;#39;)&lt;/code&gt;: 다음 상태에서 실제로 취할 행동의 가치&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;직관적 이해:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;예측 오차 &lt;code&gt;[r + γQ(s&amp;#39;,a&amp;#39;) - Q(s,a)]&lt;/code&gt;를 계산함&lt;/li&gt;
&lt;li&gt;실제 경험한 보상과 기대했던 가치의 차이를 학습에 반영함&lt;/li&gt;
&lt;li&gt;보수적 운전자가 실제 경험한 길만으로 내비게이션을 업데이트하는 방식임&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.2 Policy Gradient 방법&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;기본 목적함수:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;J(θ) = E_π[R_t] = Σ_s d^π(s) Σ_a π(a|s,θ) R^π(s,a)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;정책 경사 정리 (Policy Gradient Theorem):&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;∇_θ J(θ) = E_π[∇_θ log π(a|s,θ) Q^π(s,a)]&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;수식 분해:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;d^π(s)&lt;/code&gt;: 정책 π 하에서의 상태 분포 (얼마나 자주 그 상태에 있는가)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;∇_θ log π(a|s,θ)&lt;/code&gt;: 정책의 로그 확률에 대한 경사 (어떤 방향으로 정책을 바꿀지)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Q^π(s,a)&lt;/code&gt;: 현재 정책 하에서의 행동 가치 (그 행동이 얼마나 좋은지)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;직관적 해석:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;좋은 결과를 낸 행동의 확률은 높이고, 나쁜 결과를 낸 행동의 확률은 낮춤&lt;/li&gt;
&lt;li&gt;화가가 성공한 그림 기법은 더 자주 쓰고, 실패한 기법은 덜 쓰는 학습 과정임&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.3 REINFORCE 알고리즘&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;업데이트 공식:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;θ ← θ + α ∇_θ log π(a_t|s_t,θ) G_t&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;여기서 &lt;code&gt;G_t = Σ_{k=0}^{T-t-1} γ^k r_{t+k+1}&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;핵심 아이디어:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;에피소드가 끝난 후 전체 수익 &lt;code&gt;G_t&lt;/code&gt;를 계산함&lt;/li&gt;
&lt;li&gt;좋은 에피소드에서 한 행동들의 확률을 증가시킴&lt;/li&gt;
&lt;li&gt;몬테카를로 방식으로 분산이 큼 (운의 영향이 큼)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3. Off-Policy 방법론 심화 분석&lt;/h2&gt;
&lt;h3&gt;3.1 Q-Learning의 수학적 구조&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;벨만 최적 방정식:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Q*(s,a) = E[r + γ max_a&amp;#39; Q*(s&amp;#39;,a&amp;#39;) | s,a]&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Q-Learning 업데이트:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Q(s,a) ← Q(s,a) + α[r + γ max_a&amp;#39; Q(s&amp;#39;,a&amp;#39;) - Q(s,a)]&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;On-Policy와의 핵심 차이:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SARSA: &lt;code&gt;Q(s&amp;#39;,a&amp;#39;)&lt;/code&gt; (실제로 할 행동)&lt;/li&gt;
&lt;li&gt;Q-Learning: &lt;code&gt;max_a&amp;#39; Q(s&amp;#39;,a&amp;#39;)&lt;/code&gt; (최적 행동)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;직관적 비유:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SARSA: 실제 운전 경험으로만 길을 배움&lt;/li&gt;
&lt;li&gt;Q-Learning: 모든 가능한 길을 비교해서 최고의 길을 찾음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.2 Importance Sampling의 수학적 기초&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;문제 정의:&lt;/strong&gt;&lt;br&gt;목표 정책 π로 기댓값을 구하고 싶지만 행동 정책 b로 데이터를 수집함&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Importance Sampling 공식:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;E_π[f(X)] = E_b[f(X) * π(X)/b(X)]&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;강화학습 적용:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;E_π[G_t] = E_b[G_t * Π_{k=t}^{T-1} π(a_k|s_k)/b(a_k|s_k)]&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;직관적 해석:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;다른 사람의 경험을 내 상황에 맞게 가중치를 조정해서 활용함&lt;/li&gt;
&lt;li&gt;음식 리뷰를 볼 때 나와 취향이 비슷한 사람의 리뷰에 더 가중치를 두는 방식임&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.3 Off-Policy Actor-Critic&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;이중 네트워크 구조:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Actor: π_θ(a|s) (정책 네트워크)&lt;/li&gt;
&lt;li&gt;Critic: V_φ(s) 또는 Q_φ(s,a) (가치 네트워크)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;업데이트 공식:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;θ ← θ + α_θ ∇_θ log π_θ(a|s) [Q_φ(s,a) - V_φ(s)]
φ ← φ + α_φ ∇_φ [Q_φ(s,a) - V_φ(s)]²&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;핵심 아이디어:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Actor는 정책을 개선하고 Critic은 가치를 평가함&lt;/li&gt;
&lt;li&gt;감독(Critic)이 배우(Actor)의 연기를 평가하며 함께 발전하는 구조임&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;4. 수렴성과 안정성 분석&lt;/h2&gt;
&lt;h3&gt;4.1 On-Policy의 수렴 조건&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Robbins-Monro 조건:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Σ_t α_t = ∞, Σ_t α_t² &amp;lt; ∞&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;의미:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;학습률이 충분히 크게 누적되어야 함 (탐험 충분)&lt;/li&gt;
&lt;li&gt;학습률 제곱의 합은 유한해야 함 (수렴 보장)&lt;/li&gt;
&lt;li&gt;대표적 예: α_t = 1/t&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.2 Off-Policy의 수렴 조건&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Deadly Triad 문제:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Function Approximation (함수 근사)&lt;/li&gt;
&lt;li&gt;Bootstrapping (부트스트래핑)  &lt;/li&gt;
&lt;li&gt;Off-Policy Learning&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;수렴 어려움의 원인:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;분포 변화로 인한 불안정성&lt;/li&gt;
&lt;li&gt;Importance Sampling의 높은 분산&lt;/li&gt;
&lt;li&gt;Target Network 없이는 발산 가능성 높음&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;5. 실제 적용 시 고려사항&lt;/h2&gt;
&lt;h3&gt;5.1 탐험-활용 딜레마&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;ε-greedy 정책:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;π(a|s) = {
  1-ε+ε/|A|  if a = argmax Q(s,a)
  ε/|A|      otherwise
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;UCB (Upper Confidence Bound):&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;a_t = argmax_a [Q_t(a) + c√(ln t / N_t(a))]&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;5.2 Experience Replay의 수학적 근거&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;배치 업데이트:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;L(θ) = E_{(s,a,r,s&amp;#39;)~D}[(r + γ max_a&amp;#39; Q(s&amp;#39;,a&amp;#39;;θ^-) - Q(s,a;θ))²]&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;장점:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;데이터 효율성 증대&lt;/li&gt;
&lt;li&gt;상관관계 제거로 안정성 향상&lt;/li&gt;
&lt;li&gt;Off-Policy 학습 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;6. 결론 및 선택 가이드&lt;/h2&gt;
&lt;h3&gt;6.1 On-Policy 선택 기준&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;안정적 수렴이 중요한 경우&lt;/li&gt;
&lt;li&gt;연속적 제어 문제&lt;/li&gt;
&lt;li&gt;정책 자체가 중요한 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;6.2 Off-Policy 선택 기준&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;데이터 효율성이 중요한 경우&lt;/li&gt;
&lt;li&gt;과거 경험 재활용이 필요한 경우&lt;/li&gt;
&lt;li&gt;탐험 데이터가 제한적인 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;핵심 메시지:&lt;/strong&gt;&lt;br&gt;On-Policy는 안전한 보수적 학습, Off-Policy는 효율적이지만 불안정할 수 있는 학습임. 문제 특성에 따라 적절한 방법론을 선택하는 것이 핵심임.&lt;/p&gt;</description>
      <author>글쓰는 연구원 상티</author>
      <guid isPermaLink="true">https://researchwithme.tistory.com/12</guid>
      <comments>https://researchwithme.tistory.com/12#entry12comment</comments>
      <pubDate>Fri, 20 Jun 2025 16:53:12 +0900</pubDate>
    </item>
    <item>
      <title>Quadruped Robot Simulation Based Reinforcement Training 연구노트 (1)</title>
      <link>https://researchwithme.tistory.com/11</link>
      <description>&lt;h1&gt;필수 사전지식&lt;/h1&gt;
&lt;h2&gt;1. PPO, SAC, TD3 등 연속 제어 알고리즘&lt;/h2&gt;
&lt;h3&gt;1.1 PPO (Proximal Policy Optimization)&lt;/h3&gt;
&lt;p&gt;PPO는 정책 최적화 알고리즘 중 가장 널리 사용되는 알고리즘임. 기존 TRPO의 복잡한 제약 조건을 단순화하여 구현이 용이하면서도 안정적인 학습이 가능함.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PPO의 핵심 개념&lt;/strong&gt;: 정책 업데이트 시 이전 정책과 너무 많이 달라지지 않도록 제한하는 것임. 이를 통해 학습의 안정성을 확보함.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Clip 함수의 역할&lt;/strong&gt;: PPO는 확률 비율을 클리핑하여 정책 업데이트의 크기를 제한함. 클리핑 범위는 일반적으로 [1-ε, 1+ε]로 설정하며, ε는 0.1~0.3 범위에서 설정함.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;사족보행에서의 장점&lt;/strong&gt;: PPO는 연속적인 관절 제어에 적합하며, 복잡한 보상 구조에서도 안정적으로 학습이 가능함. 또한 병렬 환경에서의 데이터 수집에 효율적임.&lt;/p&gt;
&lt;h3&gt;1.2 SAC (Soft Actor-Critic)&lt;/h3&gt;
&lt;p&gt;SAC는 엔트로피 정규화를 통해 탐험과 활용의 균형을 자동으로 조절하는 off-policy 알고리즘임. 최대 엔트로피 강화학습의 원리를 따름.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;엔트로피 정규화&lt;/strong&gt;: 정책의 엔트로피를 목적함수에 추가하여 탐험을 장려함. 이를 통해 지역 최적점에 빠지는 것을 방지하고 더 robust한 정책을 학습함.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;자동 온도 조절&lt;/strong&gt;: SAC는 온도 파라미터 α를 자동으로 조절하여 탐험의 정도를 동적으로 결정함. 이는 수동 하이퍼파라미터 튜닝의 부담을 줄여줌.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;샘플 효율성&lt;/strong&gt;: Off-policy 특성으로 인해 경험 재생을 활용할 수 있어 샘플 효율성이 높음. 사족보행과 같이 물리 시뮬레이션 비용이 높은 환경에서 유리함.&lt;/p&gt;
&lt;h3&gt;1.3 TD3 (Twin Delayed Deep Deterministic Policy Gradient)&lt;/h3&gt;
&lt;p&gt;TD3는 DDPG의 개선된 버전으로, 세 가지 주요 기법을 통해 성능을 향상시킨 알고리즘임.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Twin Critics&lt;/strong&gt;: 두 개의 Q-함수를 사용하여 overestimation bias를 줄임. 두 Q-함수 중 작은 값을 사용하여 보수적인 추정을 수행함.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Delayed Policy Updates&lt;/strong&gt;: 정책 업데이트를 비평자 업데이트보다 적게 수행하여 정책이 불안정한 가치 함수에 의해 오도되는 것을 방지함.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Target Policy Smoothing&lt;/strong&gt;: 타겟 정책에 노이즈를 추가하여 함수 근사 오차에 대한 robustness를 증가시킨 것임.&lt;/p&gt;
&lt;h2&gt;2. Actor-Critic 구조와 정책 경사법&lt;/h2&gt;
&lt;h3&gt;2.1 Actor-Critic 구조&lt;/h3&gt;
&lt;p&gt;Actor-Critic은 정책 기반 방법과 가치 기반 방법의 장점을 결합한 구조임. Actor는 정책을 학습하고, Critic은 가치 함수를 학습함.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Actor의 역할&lt;/strong&gt;: 주어진 상태에서 어떤 행동을 취할지 결정하는 정책 π(a|s)를 학습함. 사족보행에서는 각 관절의 토크나 목표 각도를 출력함.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Critic의 역할&lt;/strong&gt;: 상태의 가치 V(s) 또는 상태-행동 쌍의 가치 Q(s,a)를 추정함. 이는 Actor의 정책 개선에 필요한 피드백을 제공함.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;상호작용&lt;/strong&gt;: Critic이 제공하는 가치 추정을 통해 Actor는 더 나은 행동을 학습하고, Actor의 행동을 통해 Critic은 더 정확한 가치 함수를 학습함.&lt;/p&gt;
&lt;h3&gt;2.2 정책 경사법 (Policy Gradient)&lt;/h3&gt;
&lt;p&gt;정책 경사법은 정책을 직접 최적화하는 방법으로, 정책 파라미터에 대한 기댓값의 gradient를 계산하여 정책을 개선함.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;REINFORCE 알고리즘&lt;/strong&gt;: 가장 기본적인 정책 경사법으로, 몬테카르로 방법을 사용하여 정책 gradient를 추정함. 하지만 높은 분산으로 인해 학습이 불안정함.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Baseline의 도입&lt;/strong&gt;: 분산을 줄이기 위해 baseline (일반적으로 가치 함수)을 사용함. 이를 통해 advantage 함수 A(s,a) = Q(s,a) - V(s)를 계산함.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;자연 정책 경사법&lt;/strong&gt;: 정책 공간의 기하학적 구조를 고려하여 더 효율적인 업데이트를 수행하는 방법임. Fisher 정보 행렬을 사용하여 자연 gradient를 계산함.&lt;/p&gt;
&lt;h2&gt;3. 경험 재생과 샘플 효율성&lt;/h2&gt;
&lt;h3&gt;3.1 경험 재생 (Experience Replay)&lt;/h3&gt;
&lt;p&gt;경험 재생은 과거의 경험을 저장했다가 재사용하는 기법으로, off-policy 알고리즘에서 핵심적인 역할을 함.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;리플레이 버퍼&lt;/strong&gt;: 경험 (s, a, r, s&amp;#39;)을 저장하는 메모리 구조임. 일반적으로 FIFO 방식으로 관리되며, 최대 크기에 도달하면 오래된 경험을 제거함.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;샘플링 전략&lt;/strong&gt;: 균등 샘플링이 기본이지만, Prioritized Experience Replay처럼 중요도에 따른 샘플링도 사용됨. TD-error가 큰 경험을 더 자주 샘플링함.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;데이터 상관관계 해결&lt;/strong&gt;: 연속적인 경험 간의 상관관계를 줄여 학습의 안정성을 향상시킴. 이는 신경망 학습에서 중요한 i.i.d 가정을 만족하게 도움.&lt;/p&gt;
&lt;h3&gt;3.2 샘플 효율성&lt;/h3&gt;
&lt;p&gt;샘플 효율성은 주어진 수의 환경 상호작용으로 얼마나 좋은 성능에 도달할 수 있는지를 나타내는 지표임.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;On-policy vs Off-policy&lt;/strong&gt;: Off-policy 방법이 일반적으로 샘플 효율성이 높음. 과거 데이터를 재사용할 수 있기 때문임.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;함수 근사의 영향&lt;/strong&gt;: 신경망과 같은 함수 근사기의 사용은 일반화 능력을 높여 샘플 효율성을 개선함.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;탐험 전략&lt;/strong&gt;: 효율적인 탐험 전략은 샘플 효율성에 직접적인 영향을 미침. UCB, Thompson Sampling 등의 방법이 사용됨.&lt;/p&gt;
&lt;h2&gt;4. 보상 함수 설계 및 희소 보상 문제&lt;/h2&gt;
&lt;h3&gt;4.1 보상 함수 설계 원칙&lt;/h3&gt;
&lt;p&gt;보상 함수는 에이전트가 학습해야 할 목표를 수치적으로 표현하는 핵심 요소임. 잘못 설계된 보상 함수는 의도하지 않은 행동을 유발할 수 있음.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;명확성&lt;/strong&gt;: 보상 함수는 원하는 행동을 명확하게 반영해야 함. 모호한 보상은 바람직하지 않은 행동을 학습시킬 수 있음.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;균형성&lt;/strong&gt;: 여러 목표 간의 균형을 맞춰야 함. 사족보행에서는 속도, 안정성, 에너지 효율성 등을 모두 고려해야 함.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Shaping&lt;/strong&gt;: 중간 목표를 통해 학습을 가이드하는 기법임. 하지만 과도한 shaping은 local optima에 빠뜨릴 수 있음.&lt;/p&gt;
&lt;h3&gt;4.2 사족보행을 위한 보상 함수 설계&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;전진 보상&lt;/strong&gt;: 로봇의 전진 속도에 비례하는 보상을 제공함. 일반적으로 목표 속도와의 차이에 반비례하도록 설계함.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;안정성 보상&lt;/strong&gt;: 몸체의 방향, 높이, 각속도 등을 통해 안정성을 평가함. 급격한 움직임이나 넘어짐에 대해 페널티를 부여함.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;에너지 효율성&lt;/strong&gt;: 관절 토크의 제곱합이나 전력 소모를 최소화하도록 하는 보상을 추가함.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;접촉 패턴&lt;/strong&gt;: 적절한 발걸음 패턴을 유도하기 위해 발과 지면의 접촉 시간과 패턴을 고려함.&lt;/p&gt;
&lt;h3&gt;4.3 희소 보상 문제 해결&lt;/h3&gt;
&lt;p&gt;희소 보상 환경에서는 보상 신호가 드물게 발생하여 학습이 어려움. 이를 해결하기 위한 다양한 방법이 개발됨.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;커리큘럼 학습&lt;/strong&gt;: 쉬운 과제부터 시작하여 점진적으로 난이도를 높이는 방법임. 사족보행에서는 평지 → 경사 → 계단 순으로 진행할 수 있음.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;모방 학습&lt;/strong&gt;: 전문가의 시연을 통해 초기 정책을 학습한 후 강화학습으로 개선하는 방법임. Behavioral Cloning이나 GAIL 같은 기법이 사용됨.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;내재적 동기&lt;/strong&gt;: 호기심이나 내재적 보상을 통해 탐험을 장려하는 방법임. Count-based exploration이나 ICM (Intrinsic Curiosity Module) 등이 있음.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;HER (Hindsight Experience Replay)&lt;/strong&gt;: 실패한 경험도 다른 목표에 대해서는 성공으로 간주하여 학습에 활용하는 방법임.&lt;/p&gt;
&lt;h2&gt;5. 구현 시 고려사항&lt;/h2&gt;
&lt;h3&gt;5.1 하이퍼파라미터 튜닝&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;학습률&lt;/strong&gt;: 너무 높으면 불안정하고, 너무 낮으면 학습이 느림. 일반적으로 3e-4 ~ 1e-3 범위에서 시작함.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;배치 크기&lt;/strong&gt;: 메모리와 계산 효율성을 고려하여 결정함. 일반적으로 64 ~ 512 사이에서 설정함.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;네트워크 구조&lt;/strong&gt;: Hidden layer의 수와 크기를 결정함. 사족보행에서는 보통 2-3개의 hidden layer와 256-512개의 뉴런을 사용함.&lt;/p&gt;
&lt;h3&gt;5.2 안정성 확보&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Gradient Clipping&lt;/strong&gt;: 기울기 폭발을 방지하기 위해 기울기의 norm을 제한함.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Target Network&lt;/strong&gt;: Q-learning에서 학습의 안정성을 위해 천천히 업데이트되는 타겟 네트워크를 사용함.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;정규화&lt;/strong&gt;: 입력 정규화와 가중치 정규화를 통해 학습의 안정성을 향상시킴.&lt;/p&gt;
&lt;p&gt;이러한 강화학습 기초 지식들을 바탕으로 Unitree Go2의 사족보행 제어를 위한 효과적인 강화학습 시스템을 구축할 수 있음. 각 알고리즘의 특성을 이해하고 문제 상황에 맞는 적절한 선택과 조합이 성공의 핵심임.&lt;/p&gt;</description>
      <author>글쓰는 연구원 상티</author>
      <guid isPermaLink="true">https://researchwithme.tistory.com/11</guid>
      <comments>https://researchwithme.tistory.com/11#entry11comment</comments>
      <pubDate>Fri, 20 Jun 2025 16:41:06 +0900</pubDate>
    </item>
    <item>
      <title>상티 2024 회고 (늦었지만 전반기 회고임)</title>
      <link>https://researchwithme.tistory.com/10</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;대학원 진학으로 인해서 학술관련 행사 참여도 반강제 ( ???: 상현아 갔다 와야겠지 ? ) 스럽게 많이 다녔고 장기간의 외주용역 계약 체결 후 작업이나 소속이전으로 인한 인수인계 레퍼런스 정리들로 하루하루 타이트 하게 지낸 것 같다.&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;2024년의 전반기 나는 무엇을 해왔을까 되돌아보는 시간..&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;1. 초음파 연구실에서의 마무리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2023년부터 의용공학부 모 초음파 연구실에 합류했었다.&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;h4 data-ke-size=&quot;size20&quot;&gt;1-1: JVM 안하고 싶었다.&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2023년엔 거의 대부분을 SpringBoot을 사용했었기에 JVM은 많이 질려있는 상태였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;허나 초기에 가장 먼저 해결 할 과제가 Java 기반 안드로이드 초음파 신호처리 앱의 성능 향상이었기에 다시 한번 Java에 손을 대게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;680&quot; data-origin-height=&quot;406&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/enkh19/btsKqNBmARo/OOUALt5ekpelJPE54y3Lw1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/enkh19/btsKqNBmARo/OOUALt5ekpelJPE54y3Lw1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/enkh19/btsKqNBmARo/OOUALt5ekpelJPE54y3Lw1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fenkh19%2FbtsKqNBmARo%2FOOUALt5ekpelJPE54y3Lw1%2Fimg.jpg&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;680&quot; height=&quot;406&quot; data-origin-width=&quot;680&quot; data-origin-height=&quot;406&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;p data-ke-size=&quot;size16&quot;&gt;벌크 형태로 들어오는 초음파 raw Data를 실시간 프레임 단위로 시각화 하는 프로젝트였는데, 이미 레거시로 일부 도메인 로직은 구현되어있었으나 렌더링을 하는 과정에서 성능저하들이 있었기에 몇차례 성능 개선을 통해서 버퍼링, 화질 개선을 진행했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2024-10-31 at 1.05.36 AM.png&quot; data-origin-width=&quot;1376&quot; data-origin-height=&quot;660&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BOAdK/btsKrbu16Y5/XDeHmc28uMgK9z2QbjCef0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BOAdK/btsKrbu16Y5/XDeHmc28uMgK9z2QbjCef0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BOAdK/btsKrbu16Y5/XDeHmc28uMgK9z2QbjCef0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBOAdK%2FbtsKrbu16Y5%2FXDeHmc28uMgK9z2QbjCef0%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;1376&quot; height=&quot;660&quot; data-filename=&quot;Screenshot 2024-10-31 at 1.05.36 AM.png&quot; data-origin-width=&quot;1376&quot; data-origin-height=&quot;660&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2024-10-31 at 1.05.49 AM.png&quot; data-origin-width=&quot;1822&quot; data-origin-height=&quot;616&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dlokcX/btsKoZXAnwn/qKiflaonZs4krhy2oqAuK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dlokcX/btsKoZXAnwn/qKiflaonZs4krhy2oqAuK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dlokcX/btsKoZXAnwn/qKiflaonZs4krhy2oqAuK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdlokcX%2FbtsKoZXAnwn%2FqKiflaonZs4krhy2oqAuK0%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;1822&quot; height=&quot;616&quot; data-filename=&quot;Screenshot 2024-10-31 at 1.05.49 AM.png&quot; data-origin-width=&quot;1822&quot; data-origin-height=&quot;616&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2024-10-31 at 1.07.32 AM.png&quot; data-origin-width=&quot;1846&quot; data-origin-height=&quot;1438&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AVYsU/btsKpaEWXiT/s4JD8COVpgfNBYyzeBRVlK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AVYsU/btsKpaEWXiT/s4JD8COVpgfNBYyzeBRVlK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AVYsU/btsKpaEWXiT/s4JD8COVpgfNBYyzeBRVlK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAVYsU%2FbtsKpaEWXiT%2Fs4JD8COVpgfNBYyzeBRVlK%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;1846&quot; height=&quot;1438&quot; data-filename=&quot;Screenshot 2024-10-31 at 1.07.32 AM.png&quot; data-origin-width=&quot;1846&quot; data-origin-height=&quot;1438&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;p data-ke-size=&quot;size16&quot;&gt;사실 이제와서 보니까 뭘 했는지 가물가물 하다만 나름 재미있었던 순간이 많았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 근본적인 문제는 JVM 기반 OpenCV의 성능 오버헤드, 성능저하였기에 간접적인 요행 (?) 으로는 해당 버퍼링을 해결할 수 없었고&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;686&quot; data-origin-height=&quot;386&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjH9wh/btsKrerJKyc/tHTiqLpmDcr3cWvz2ZmfD1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjH9wh/btsKrerJKyc/tHTiqLpmDcr3cWvz2ZmfD1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjH9wh/btsKrerJKyc/tHTiqLpmDcr3cWvz2ZmfD1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjH9wh%2FbtsKrerJKyc%2FtHTiqLpmDcr3cWvz2ZmfD1%2Fimg.jpg&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;686&quot; height=&quot;386&quot; data-origin-width=&quot;686&quot; data-origin-height=&quot;386&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;p data-ke-size=&quot;size16&quot;&gt;최종적으로 중요 도메인 로직을 JNI를 사용한 C++ 기반 로직으로 변경해야한다는 암울한 결과를 내릴 수 밖에 없었는데...&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bm8kip/btsKqBBa0TF/VkmkrCZ9gBUBv95dg36M61/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bm8kip/btsKqBBa0TF/VkmkrCZ9gBUBv95dg36M61/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bm8kip/btsKqBBa0TF/VkmkrCZ9gBUBv95dg36M61/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbm8kip%2FbtsKqBBa0TF%2FVkmkrCZ9gBUBv95dg36M61%2Fimg.jpg&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;1280&quot; height=&quot;720&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&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;p data-ke-size=&quot;size16&quot;&gt;딱봐도 알겠지마는 DX를 단 하나도 고려하지 않은 어마어마한 이식 난이도와 복잡함에 의욕을 상실해버렸고 또 다른 요행(바라지 말라며)을 찾아보았는데...&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1125&quot; data-origin-height=&quot;1224&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eb5EIa/btsKpmkM6V6/R1E426DDvkkxs2pVcYsH0k/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eb5EIa/btsKpmkM6V6/R1E426DDvkkxs2pVcYsH0k/img.jpg&quot; data-alt=&quot;인새앵..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eb5EIa/btsKpmkM6V6/R1E426DDvkkxs2pVcYsH0k/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Feb5EIa%2FbtsKpmkM6V6%2FR1E426DDvkkxs2pVcYsH0k%2Fimg.jpg&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;400&quot; height=&quot;435&quot; data-origin-width=&quot;1125&quot; data-origin-height=&quot;1224&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;인새앵..&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러다가 Rust 만지게 됐습니다.&lt;/p&gt;
&lt;figure id=&quot;og_1730303902500&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Rust기반 멀티플랫폼 개발기 (1)&quot; data-og-description=&quot;갑자기 어떤 경위로.. 무슨 Rust를 만지느냐 필자는 보안을 갖다버린 후 Java, Python 이외에는 크게 언어를 만진적이 없다. 심지어 익스또한 파이썬으로 짜기 때문에 읽는 것을 제외하고 작성하는 &quot; data-og-host=&quot;sxngt.github.io&quot; data-og-source-url=&quot;https://sxngt.github.io/Rust%EA%B8%B0%EB%B0%98%20%EB%A9%80%ED%8B%B0%ED%94%8C%EB%9E%AB%ED%8F%BC%20%EA%B0%9C%EB%B0%9C%EA%B8%B0/&quot; data-og-url=&quot;https://sxngt.github.io/Rust기반 멀티플랫폼 개발기/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/8Trmb/hyXpo3Jeyq/e6syrnJXhRxKEwJWkOq2Zk/img.png?width=563&amp;amp;height=580&amp;amp;face=212_193_375_357,https://scrap.kakaocdn.net/dn/JfZpg/hyXpwOcYGU/iekSlVNGYRV1ag49IcSCYk/img.png?width=614&amp;amp;height=1412&amp;amp;face=0_0_614_1412,https://scrap.kakaocdn.net/dn/btBuT0/hyXpDNiC32/2RqzNm894aQhg9PdktCKYk/img.png?width=1420&amp;amp;height=500&amp;amp;face=0_0_1420_500&quot;&gt;&lt;a href=&quot;https://sxngt.github.io/Rust%EA%B8%B0%EB%B0%98%20%EB%A9%80%ED%8B%B0%ED%94%8C%EB%9E%AB%ED%8F%BC%20%EA%B0%9C%EB%B0%9C%EA%B8%B0/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sxngt.github.io/Rust%EA%B8%B0%EB%B0%98%20%EB%A9%80%ED%8B%B0%ED%94%8C%EB%9E%AB%ED%8F%BC%20%EA%B0%9C%EB%B0%9C%EA%B8%B0/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/8Trmb/hyXpo3Jeyq/e6syrnJXhRxKEwJWkOq2Zk/img.png?width=563&amp;amp;height=580&amp;amp;face=212_193_375_357,https://scrap.kakaocdn.net/dn/JfZpg/hyXpwOcYGU/iekSlVNGYRV1ag49IcSCYk/img.png?width=614&amp;amp;height=1412&amp;amp;face=0_0_614_1412,https://scrap.kakaocdn.net/dn/btBuT0/hyXpDNiC32/2RqzNm894aQhg9PdktCKYk/img.png?width=1420&amp;amp;height=500&amp;amp;face=0_0_1420_500');&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;Rust기반 멀티플랫폼 개발기 (1)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;갑자기 어떤 경위로.. 무슨 Rust를 만지느냐 필자는 보안을 갖다버린 후 Java, Python 이외에는 크게 언어를 만진적이 없다. 심지어 익스또한 파이썬으로 짜기 때문에 읽는 것을 제외하고 작성하는&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sxngt.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;겟츠비 다루는게 좀 어리숙해서 썸네일이 이상하다마는 적다만 글이 있다. ( 블로그 난민 시절을 겪다보니 여기저기 흩어져있음)&lt;/p&gt;
&lt;figure id=&quot;og_1730304935783&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - sxngt/MITS_Rust_Multiplatform_SDK: MITS Rust기반 다언어 지원 SDK 제너레이터&quot; data-og-description=&quot;MITS Rust기반 다언어 지원 SDK 제너레이터. Contribute to sxngt/MITS_Rust_Multiplatform_SDK development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/sxngt/MITS_Rust_Multiplatform_SDK&quot; data-og-url=&quot;https://github.com/sxngt/MITS_Rust_Multiplatform_SDK&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/17xaq/hyXptYeNSI/Z7g3KQwymQ5Kl6CVhcnI51/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/HP2pu/hyXpC1X5lE/KkAbQAz3k9AE0rF7OIprVk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/sxngt/MITS_Rust_Multiplatform_SDK&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/sxngt/MITS_Rust_Multiplatform_SDK&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/17xaq/hyXptYeNSI/Z7g3KQwymQ5Kl6CVhcnI51/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/HP2pu/hyXpC1X5lE/KkAbQAz3k9AE0rF7OIprVk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&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;GitHub - sxngt/MITS_Rust_Multiplatform_SDK: MITS Rust기반 다언어 지원 SDK 제너레이터&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;MITS Rust기반 다언어 지원 SDK 제너레이터. Contribute to sxngt/MITS_Rust_Multiplatform_SDK development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2024-10-31 at 1.16.29 AM.png&quot; data-origin-width=&quot;1834&quot; data-origin-height=&quot;2028&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPm1JS/btsKpl7irLy/xB8obN7gPzhiIzH6qp8hQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPm1JS/btsKpl7irLy/xB8obN7gPzhiIzH6qp8hQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPm1JS/btsKpl7irLy/xB8obN7gPzhiIzH6qp8hQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPm1JS%2FbtsKpl7irLy%2FxB8obN7gPzhiIzH6qp8hQ0%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;1834&quot; height=&quot;2028&quot; data-filename=&quot;Screenshot 2024-10-31 at 1.16.29 AM.png&quot; data-origin-width=&quot;1834&quot; data-origin-height=&quot;2028&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;p data-ke-size=&quot;size16&quot;&gt;러스트 로직을 쉘스크립트를 통해 쉽게 쉽게 이전할 수 있지만 사실 이것보다 중요했던건 인간 GC가 되어 신호처리 로직을 PORO (플레인 올드 러스트 옵젴... 사실 없는말임)으로 짜야 하는 점, 이후 중요한 Matrix 연산처리를 Rust-OpenCV로 처리하고 각 아키텍쳐에 맞게 빌드해야하는 삽질들이 더 힘들었지만 괜찮은 인프라 하나 남겨두고 갔다는 점이 보람찼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1-2. 딥러닝도 해야겠더라&lt;/h4&gt;
&lt;figure id=&quot;og_1730305229464&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;대학원생인데 딥러닝 머신이 왔다면(1)&quot; data-og-description=&quot;난 왜 이 글을 쓰고 있는가에 대하여 항상 느끼는건데 딥러닝 레퍼런스는 항상 부족하다. 보통 오류메세지에 대한 해결 방법이나 삽질기, TIL 뉘앙스에 스무뜨 하게 묻어나오는 그런 레퍼런스들&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@sxngt/%EB%8C%80%ED%95%99%EC%9B%90%EC%83%9D%EC%9D%B8%EB%8D%B0-%EB%94%A5%EB%9F%AC%EB%8B%9D-%EB%A8%B8%EC%8B%A0%EC%9D%B4-%EC%99%94%EB%8B%A4%EB%A9%B4&quot; data-og-url=&quot;https://velog.io/@sxngt/대학원생인데-딥러닝-머신이-왔다면&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bz2BZ9/hyXprF8SUv/VXpkuFD3t5yur5uvDjTVN1/img.png?width=2550&amp;amp;height=1514&amp;amp;face=0_0_2550_1514,https://scrap.kakaocdn.net/dn/fPPKv/hyXpqmWmqt/79MrFzkS6G4HCKYpSy90vk/img.png?width=2550&amp;amp;height=1514&amp;amp;face=0_0_2550_1514&quot;&gt;&lt;a href=&quot;https://velog.io/@sxngt/%EB%8C%80%ED%95%99%EC%9B%90%EC%83%9D%EC%9D%B8%EB%8D%B0-%EB%94%A5%EB%9F%AC%EB%8B%9D-%EB%A8%B8%EC%8B%A0%EC%9D%B4-%EC%99%94%EB%8B%A4%EB%A9%B4&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@sxngt/%EB%8C%80%ED%95%99%EC%9B%90%EC%83%9D%EC%9D%B8%EB%8D%B0-%EB%94%A5%EB%9F%AC%EB%8B%9D-%EB%A8%B8%EC%8B%A0%EC%9D%B4-%EC%99%94%EB%8B%A4%EB%A9%B4&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bz2BZ9/hyXprF8SUv/VXpkuFD3t5yur5uvDjTVN1/img.png?width=2550&amp;amp;height=1514&amp;amp;face=0_0_2550_1514,https://scrap.kakaocdn.net/dn/fPPKv/hyXpqmWmqt/79MrFzkS6G4HCKYpSy90vk/img.png?width=2550&amp;amp;height=1514&amp;amp;face=0_0_2550_1514');&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;대학원생인데 딥러닝 머신이 왔다면(1)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;난 왜 이 글을 쓰고 있는가에 대하여 항상 느끼는건데 딥러닝 레퍼런스는 항상 부족하다. 보통 오류메세지에 대한 해결 방법이나 삽질기, TIL 뉘앙스에 스무뜨 하게 묻어나오는 그런 레퍼런스들&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2024-10-31 at 1.21.59 AM.png&quot; data-origin-width=&quot;1352&quot; data-origin-height=&quot;576&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFBvkB/btsKpnquL4p/mUvzWl2dCLJxKUBboNQvU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFBvkB/btsKpnquL4p/mUvzWl2dCLJxKUBboNQvU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFBvkB/btsKpnquL4p/mUvzWl2dCLJxKUBboNQvU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFBvkB%2FbtsKpnquL4p%2FmUvzWl2dCLJxKUBboNQvU1%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;1352&quot; height=&quot;576&quot; data-filename=&quot;Screenshot 2024-10-31 at 1.21.59 AM.png&quot; data-origin-width=&quot;1352&quot; data-origin-height=&quot;576&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;p data-ke-size=&quot;size16&quot;&gt;나는 Vim충이라서 CLI말고 필요 없었다만 대의를 위한 code-server도 설치해보고, Conda 집어던지기와 동시에 pyenv로 쾌적한 환경 설계 등등 재밌는 경험은 많이 했다만 개발에 관련된 파트는 아니라서 넘어가겠다.&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;생각보다 code-server가 좋았는데, 그 만큼 아쉬운 경험들도 많았어서 추후 시간이 나면 fork 떠서 조금 더 성향에 맞게 다듬어보고싶다.&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;h3 data-ke-size=&quot;size23&quot;&gt;2.&amp;nbsp; 재미있는 ( 하하 ) 보안 학술대회&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부전공이 산업보안인데, 사실 다전공 학사였기에 골랐을 뿐 나의 부전공에 대하여 전혀 인식이 없었지만 졸업을 위해서라면 전공과목을 이수했어야 했고, 가장 간단하고 직관적으로 좋은 성적을 내기 위한 방법은 논문 제출 후 학술대회 수상.. 으로 A+과 맞교환 하는 교수님과의 어엄청난 딜이 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;365&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eCOpPV/btsKq1TG3sR/KelNP9hRkbBFkKrnrGHu70/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eCOpPV/btsKq1TG3sR/KelNP9hRkbBFkKrnrGHu70/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eCOpPV/btsKq1TG3sR/KelNP9hRkbBFkKrnrGHu70/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeCOpPV%2FbtsKq1TG3sR%2FKelNP9hRkbBFkKrnrGHu70%2Fimg.jpg&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;512&quot; height=&quot;365&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;365&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;p data-ke-size=&quot;size16&quot;&gt;이전 직장에서 연구 개발했던 MPTCP 관련 주제로 논문을 작성할까 싶었지만 학사신분으로 나가는 학술대회이기도 했고 웹 / 클라우드 개발부문으로 접수했기에 너무 고전적인 보안보단 웹개발을 하는 과정에서 생길 수 있는 보안 허점에 대한 인식 개선을 주제로 라이트하게 작성했다.&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;/h3&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-origin-width=&quot;1125&quot; data-origin-height=&quot;2436&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/O47vd/btsKp2lyv5F/LS5o2qgnrxgqiWVBwKTKKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/O47vd/btsKp2lyv5F/LS5o2qgnrxgqiWVBwKTKKK/img.png&quot; data-alt=&quot;어메이징 ㅋㅋ&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/O47vd/btsKp2lyv5F/LS5o2qgnrxgqiWVBwKTKKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FO47vd%2FbtsKp2lyv5F%2FLS5o2qgnrxgqiWVBwKTKKK%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;300&quot; height=&quot;650&quot; data-origin-width=&quot;1125&quot; data-origin-height=&quot;2436&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;어메이징 ㅋㅋ&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 낭만적인 여행 기회를 선사해주시는 하늘의 가르킴에 눈물이 나기도 전에 해당 루트는 새벽 5시 기상하여 첫차를 타도 발표시간 전에 도착하지 못하기 때문에 몹시 럭키비키한 마인드셋으로 이틀전부터 기차여행을 떠나버렸다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n6NHg/btsKqvAZm5d/KIzK2fegYYl4QeykpqSRjK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n6NHg/btsKqvAZm5d/KIzK2fegYYl4QeykpqSRjK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n6NHg/btsKqvAZm5d/KIzK2fegYYl4QeykpqSRjK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn6NHg%2FbtsKqvAZm5d%2FKIzK2fegYYl4QeykpqSRjK%2Fimg.jpg&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;400&quot; height=&quot;533&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/doIb8N/btsKoYxCRui/KOVBuWgUtE6qwmChx4FGJ0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/doIb8N/btsKoYxCRui/KOVBuWgUtE6qwmChx4FGJ0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/doIb8N/btsKoYxCRui/KOVBuWgUtE6qwmChx4FGJ0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdoIb8N%2FbtsKoYxCRui%2FKOVBuWgUtE6qwmChx4FGJ0%2Fimg.jpg&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;400&quot; height=&quot;533&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&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;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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beUp0I/btsKp1Ure0C/BOoCT4Q3EBsBbor35RjjD1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beUp0I/btsKp1Ure0C/BOoCT4Q3EBsBbor35RjjD1/img.jpg&quot; data-alt=&quot;카이의 아들 김수성씨 특별출현&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beUp0I/btsKp1Ure0C/BOoCT4Q3EBsBbor35RjjD1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeUp0I%2FbtsKp1Ure0C%2FBOoCT4Q3EBsBbor35RjjD1%2Fimg.jpg&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;400&quot; height=&quot;533&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;카이의 아들 김수성씨 특별출현&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&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-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UdtX2/btsKrdM8fq4/VlSCjYwUeXaS6H2kVr58U1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UdtX2/btsKrdM8fq4/VlSCjYwUeXaS6H2kVr58U1/img.jpg&quot; data-alt=&quot;캬 이게 진짜 내츄럴이지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UdtX2/btsKrdM8fq4/VlSCjYwUeXaS6H2kVr58U1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUdtX2%2FbtsKrdM8fq4%2FVlSCjYwUeXaS6H2kVr58U1%2Fimg.jpg&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;300&quot; height=&quot;225&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;캬 이게 진짜 내츄럴이지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c9wBL2/btsKo0hSNtG/XWR84g3sgZUeL3A75uOLL1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c9wBL2/btsKo0hSNtG/XWR84g3sgZUeL3A75uOLL1/img.jpg&quot; data-alt=&quot;목포도 함 찍어주고&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c9wBL2/btsKo0hSNtG/XWR84g3sgZUeL3A75uOLL1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc9wBL2%2FbtsKo0hSNtG%2FXWR84g3sgZUeL3A75uOLL1%2Fimg.jpg&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;400&quot; height=&quot;400&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;목포도 함 찍어주고&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2873&quot; data-origin-height=&quot;2990&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dNvhrb/btsKqA98OLp/eK5uM40HroNWtU2J904UUk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dNvhrb/btsKqA98OLp/eK5uM40HroNWtU2J904UUk/img.jpg&quot; data-alt=&quot;게스트 하우스에서 잠도 자고&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dNvhrb/btsKqA98OLp/eK5uM40HroNWtU2J904UUk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdNvhrb%2FbtsKqA98OLp%2FeK5uM40HroNWtU2J904UUk%2Fimg.jpg&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;300&quot; height=&quot;312&quot; data-origin-width=&quot;2873&quot; data-origin-height=&quot;2990&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;게스트 하우스에서 잠도 자고&lt;/figcaption&gt;
&lt;/figure&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박3일 기차여행을 마치고 나주에 도착해서 바로 프레젠테이션을 시작했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7jE6X/btsKpQFE9gc/GkoYzQVkduCWQai4W9m0k0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7jE6X/btsKpQFE9gc/GkoYzQVkduCWQai4W9m0k0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7jE6X/btsKpQFE9gc/GkoYzQVkduCWQai4W9m0k0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7jE6X%2FbtsKpQFE9gc%2FGkoYzQVkduCWQai4W9m0k0%2Fimg.jpg&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;400&quot; height=&quot;533&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eCoE3e/btsKpSXTYDG/Xm3LgEp84g0p68izgL0ntK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eCoE3e/btsKpSXTYDG/Xm3LgEp84g0p68izgL0ntK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eCoE3e/btsKpSXTYDG/Xm3LgEp84g0p68izgL0ntK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeCoE3e%2FbtsKpSXTYDG%2FXm3LgEp84g0p68izgL0ntK%2Fimg.jpg&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;600&quot; height=&quot;450&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&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;p data-ke-size=&quot;size16&quot;&gt;보면 알겠다시피 gRPC 기반으로 MSA 서버를 구축할때 정말 기본적인 설정값들로 통신하는형태의 구현형태 레퍼런스가 매우 많은 것 같아서 그런 형태로 진행했을시의 워스트 시나리오를 시현하고 최소한의 보안구현 요구스펙을 소개하는 PT를 가졌다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1125&quot; data-origin-height=&quot;1167&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bf7mGD/btsKp69nXqL/R1bkaARpLMX8HVD7ML6KaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bf7mGD/btsKp69nXqL/R1bkaARpLMX8HVD7ML6KaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bf7mGD/btsKp69nXqL/R1bkaARpLMX8HVD7ML6KaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbf7mGD%2FbtsKp69nXqL%2FR1bkaARpLMX8HVD7ML6KaK%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;500&quot; height=&quot;519&quot; data-origin-width=&quot;1125&quot; data-origin-height=&quot;1167&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;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-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/F1kxo/btsKqPsrfp0/WXdWkKDZXokZkKTIjsQ5G1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/F1kxo/btsKqPsrfp0/WXdWkKDZXokZkKTIjsQ5G1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/F1kxo/btsKqPsrfp0/WXdWkKDZXokZkKTIjsQ5G1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FF1kxo%2FbtsKqPsrfp0%2FWXdWkKDZXokZkKTIjsQ5G1%2Fimg.jpg&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;500&quot; height=&quot;375&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&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;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-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9VGqu/btsKqQSqmsD/Nj1JgnMKAJU6jCzaLgcD1K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9VGqu/btsKqQSqmsD/Nj1JgnMKAJU6jCzaLgcD1K/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9VGqu/btsKqQSqmsD/Nj1JgnMKAJU6jCzaLgcD1K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9VGqu%2FbtsKqQSqmsD%2FNj1JgnMKAJU6jCzaLgcD1K%2Fimg.jpg&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;600&quot; height=&quot;450&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&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;p data-ke-size=&quot;size16&quot;&gt;TLS 암호화와 각 클라이언트 - 서버간의 더블체크에 대한 필요성을 강조하고 구현을 설명하며 끝난 발표..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발표 이후 생각보다 촘촘하고 거센 QnA에서 살짝 당황했지만 실제 개발하고 경험했던 시나리오를 기반으로 작성한 PT이다보니 임기응변에 꽤 능했던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4284&quot; data-origin-height=&quot;5712&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dq991y/btsKqJy1xJs/Km5UJNwdNR6L8VeXoBz6B1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dq991y/btsKqJy1xJs/Km5UJNwdNR6L8VeXoBz6B1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dq991y/btsKqJy1xJs/Km5UJNwdNR6L8VeXoBz6B1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdq991y%2FbtsKqJy1xJs%2FKm5UJNwdNR6L8VeXoBz6B1%2Fimg.jpg&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;300&quot; height=&quot;400&quot; data-origin-width=&quot;4284&quot; data-origin-height=&quot;5712&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;아무튼 그렇게 수상엔딩&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;물론 이젠 많이 거리가 멀어진 보안과 다시 손잡아본 경험과 오랜만의 PT도 인상깊은 경험이었지만, 가장 좋았던 순간들과 경험들은 혼자 여러 곳을 돌아다니고 사색하며 얻은 내적 성숙함과 묵혀놨던 잡념을 꺼내어 모두 풀어놓을 수 있었던 &quot;여행&quot;이 가장 뜯깊었던 것 같다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;3. 재미있는 ( 하하 ) 넷제로 해커톤&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 대학원 지도교수님께서 가볍게 ( 매우 무겁게 ) 제안해주신 덕에 SW융합대학 소속 대학생들을 대상으로 진행하는 넷제로 해커톤에 참여하게 되었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;1413&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cYRMxo/btsKpVmHOlJ/ejBUQmyzmcIzVYrVCrHMik/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cYRMxo/btsKpVmHOlJ/ejBUQmyzmcIzVYrVCrHMik/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cYRMxo/btsKpVmHOlJ/ejBUQmyzmcIzVYrVCrHMik/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcYRMxo%2FbtsKpVmHOlJ%2FejBUQmyzmcIzVYrVCrHMik%2Fimg.jpg&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;300&quot; height=&quot;424&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;1413&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;p data-ke-size=&quot;size16&quot;&gt;숙식 - 경비지원이 있었기에 가벼운 마음으로 갈 수 있었다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cO38oh/btsKq1zo5WB/QfdnK7fY34Cd4Xd3zHqXGk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cO38oh/btsKq1zo5WB/QfdnK7fY34Cd4Xd3zHqXGk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cO38oh/btsKq1zo5WB/QfdnK7fY34Cd4Xd3zHqXGk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcO38oh%2FbtsKq1zo5WB%2FQfdnK7fY34Cd4Xd3zHqXGk%2Fimg.jpg&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;500&quot; height=&quot;667&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&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;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-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pBl2T/btsKp6axmRd/1x1JeelisqORBiljUVSShK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pBl2T/btsKp6axmRd/1x1JeelisqORBiljUVSShK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pBl2T/btsKp6axmRd/1x1JeelisqORBiljUVSShK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpBl2T%2FbtsKp6axmRd%2F1x1JeelisqORBiljUVSShK%2Fimg.jpg&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;300&quot; height=&quot;400&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&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;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-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bD5Bom/btsKqrrR5om/1pzzKIhR8SbJoxkaywp7P1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bD5Bom/btsKqrrR5om/1pzzKIhR8SbJoxkaywp7P1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bD5Bom/btsKqrrR5om/1pzzKIhR8SbJoxkaywp7P1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbD5Bom%2FbtsKqrrR5om%2F1pzzKIhR8SbJoxkaywp7P1%2Fimg.jpg&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;600&quot; height=&quot;450&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&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;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-origin-width=&quot;1125&quot; data-origin-height=&quot;1081&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Jsvui/btsKqMWOIS0/ZMFdXu19lN85OC8N1zSc01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Jsvui/btsKqMWOIS0/ZMFdXu19lN85OC8N1zSc01/img.png&quot; data-alt=&quot;지금부터 밤샘지옥 시작&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Jsvui/btsKqMWOIS0/ZMFdXu19lN85OC8N1zSc01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJsvui%2FbtsKqMWOIS0%2FZMFdXu19lN85OC8N1zSc01%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;600&quot; height=&quot;577&quot; data-origin-width=&quot;1125&quot; data-origin-height=&quot;1081&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;지금부터 밤샘지옥 시작&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환경수호가 개발주제였기 때문에 (자세히는 탄소중립) 우리팀은 &quot;업사이클링 마일리지를 통한 구매유도 쇼핑몰&quot;을 주제로 선정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS에서 주관한 대회였기에 AWS의 각종 서비스가 개발과정에서 공짜였음으로 AWS Lambda, S3, RDS, EC2 등등 야무지게 써가면서 MSA환경을 구축했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1125&quot; data-origin-height=&quot;2436&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VvQ1b/btsKrdflgLf/aPMbkUNGb7Ahck25aNipR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VvQ1b/btsKrdflgLf/aPMbkUNGb7Ahck25aNipR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VvQ1b/btsKrdflgLf/aPMbkUNGb7Ahck25aNipR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVvQ1b%2FbtsKrdflgLf%2FaPMbkUNGb7Ahck25aNipR0%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;200&quot; height=&quot;433&quot; data-origin-width=&quot;1125&quot; data-origin-height=&quot;2436&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1125&quot; data-origin-height=&quot;2436&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c1FvVs/btsKpB2594y/bNoPOA9QkmaPUZk0frzhNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c1FvVs/btsKpB2594y/bNoPOA9QkmaPUZk0frzhNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c1FvVs/btsKpB2594y/bNoPOA9QkmaPUZk0frzhNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc1FvVs%2FbtsKpB2594y%2FbNoPOA9QkmaPUZk0frzhNK%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;200&quot; height=&quot;433&quot; data-origin-width=&quot;1125&quot; data-origin-height=&quot;2436&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;p data-ke-size=&quot;size16&quot;&gt;각 쇼핑몰에서 업사이클링에 적합한 항목을 크롤링해서 DB에 적재하고 각 상품에 대한 업사이클링, 환경수호에 긍정적인 영향을 미치는 에코 지수를 LLM 기반 모델이 점수를 책정하여 추천항목에 올리는 형태의 마일리지 퀘스트형 쇼핑 앱을 완성했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cibh06/btsKpQ6RH7q/JQ7IpMLIEop4MeNsVjaRSK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cibh06/btsKpQ6RH7q/JQ7IpMLIEop4MeNsVjaRSK/img.jpg&quot; data-alt=&quot;팀장은.. 역시 발표를..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cibh06/btsKpQ6RH7q/JQ7IpMLIEop4MeNsVjaRSK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcibh06%2FbtsKpQ6RH7q%2FJQ7IpMLIEop4MeNsVjaRSK%2Fimg.jpg&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;400&quot; height=&quot;300&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;팀장은.. 역시 발표를..&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dbt25y/btsKrekYC9X/zX50TKFVpNFCABoKoIz9J1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dbt25y/btsKrekYC9X/zX50TKFVpNFCABoKoIz9J1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dbt25y/btsKrekYC9X/zX50TKFVpNFCABoKoIz9J1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdbt25y%2FbtsKrekYC9X%2FzX50TKFVpNFCABoKoIz9J1%2Fimg.jpg&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;500&quot; height=&quot;375&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&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;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;그래도 좋았던 부분은 개발을 해보지 않은 24학번 팀원후배는 기획과 디자인에 열심히 기여할 수 있는 분위기를 만들어 주어서 팀의 응집도를 많이 늘렸고, 파이썬을 할 수 있었던 나머지 두 팀원들에게 크롤러 튜토리얼과 약간의 튜터링으로 크롤러 서버 개발의 중요 로직에 기여할 수 있도록 하여 모두가 프로덕트의 중요한 부분에 기여할 수 있도록 완급조절하여 모두가 성취감을 느낄 수 있었다는 점이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&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-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uLCx3/btsKp2MCMVV/2mQCasNdiK2g6xGUoW3Y5K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uLCx3/btsKp2MCMVV/2mQCasNdiK2g6xGUoW3Y5K/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uLCx3/btsKp2MCMVV/2mQCasNdiK2g6xGUoW3Y5K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuLCx3%2FbtsKp2MCMVV%2F2mQCasNdiK2g6xGUoW3Y5K%2Fimg.jpg&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;200&quot; height=&quot;267&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&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;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;수상엔딩&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=xQJcuBGjKwU&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/cOiUlY/hyXpC8Jnm0/W8Eh2sFOPELtkbTulqf4vk/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/bdZ6OJ/hyXpvu0CNJ/2MvJLxYkxVPqf6q12mx5XK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;2024  INHA SW NET-Zero 공동해커톤 현장스케치 (prod. 황지호)&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/xQJcuBGjKwU&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption&gt;자세히 보다보면 내 뒷통수가 좀 보이지 않을까&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 제법 큰 사이즈의 외주개발&lt;/h3&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;적다보니 티스토리 에디터가 못버텨서 너무 버벅거리니 쫄림으로 2편에서 더 작성하겠다...&lt;/p&gt;</description>
      <author>글쓰는 연구원 상티</author>
      <guid isPermaLink="true">https://researchwithme.tistory.com/10</guid>
      <comments>https://researchwithme.tistory.com/10#entry10comment</comments>
      <pubDate>Thu, 31 Oct 2024 02:23:14 +0900</pubDate>
    </item>
  </channel>
</rss>