728x90

nextjs nextauth를 활용해서 인증시스템을 구현하기 위한 가장 좋은 조합은?


Next.js와 NextAuth.js를 활용해 안정적이고 확장 가능한 인증 시스템을 구축하기 위한 최적의 조합은 다음과 같습니다. 각 레이어별 접근 방식을 조합해 보안, 성능, 유지보수성을 균형 있게 확보할 수 있습니다.

1. 미들웨어(Middleware): 전역 인증 검사
역할
모든 라우트 진입 전 인증 상태 확인: 사용자가 보호된 경로에 접근할 때마다 세션 유효성을 검사합니다.

기본 리디렉션 처리: 미인증 사용자를 로그인 페이지로 자동 이동시킵니다.

구현
typescript
// middleware.ts
import NextAuth from 'next-auth';
import { authConfig } from './auth.config';

export default NextAuth(authConfig).auth;

export const config = {
  matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
};
typescript
// auth.config.ts
import type { NextAuthConfig } from 'next-auth';

export const authConfig = {
  pages: { signIn: '/login' },
  callbacks: {
    authorized({ auth, request }) {
      const isLoggedIn = !!auth?.user;
      const isProtectedRoute = request.nextUrl.pathname.startsWith('/dashboard');
      return isProtectedRoute ? isLoggedIn : true;
    },
  },
  providers: [], // OAuth 또는 Credentials 공급자 추가
} satisfies NextAuthConfig;
장점
성능 최적화: 라우트 렌더링 전에 차단하여 불필요한 작업 방지.

일관된 정책 적용: 모든 보호된 경로에 동일한 규칙 적용.

2. 서버 컴포넌트: 세부 권한 관리
역할
역할 기반 접근 제어(RBAC): 사용자 역할(admin, user)에 따라 콘텐츠 제한.

서버 측 데이터 페칭: 민감한 데이터를 클라이언트에 노출 없이 처리.

구현
typescript
// app/dashboard/page.tsx
import { getServerSession } from 'next-auth';
import { authOptions } from '@/app/api/auth/[...nextauth]/route';

export default async function Dashboard() {
  const session = await getServerSession(authOptions);
  
  if (!session?.user) redirect('/login');
  if (session.user.role !== 'admin') redirect('/unauthorized');

  const data = await fetchSensitiveData(); // 서버에서 직접 호출
  return <AdminContent data={data} />;
}
장점
SEO 친화적: 서버에서 완성된 HTML 제공.

보안 강화: 권한 검증을 서버에서 수행.

3. API 라우트: 백엔드 보호
역할
엔드포인트 보안 강화: 클라이언트-서버 통신 시 이중 검증.

데이터 무결성 보장: API 수준에서 인증/권한 확인.

구현
typescript
// app/api/admin/route.ts
import { getServerSession } from 'next-auth';
import { authOptions } from '../auth/[...nextauth]/route';

export async function GET() {
  const session = await getServerSession(authOptions);
  if (!session?.user || session.user.role !== 'admin') {
    return new Response('Unauthorized', { status: 401 });
  }
  return Response.json({ data: '관리자 전용 데이터' });
}
장점
무단 접근 차단: 직접 API 호출 시에도 보호.

유연한 에러 처리: 맞춤형 상태 코드 반환.

4. 클라이언트 컴포넌트: 실시간 UX
역할
동적 UI 업데이트: 로그인 상태에 따른 버튼/메뉴 변경.

로딩 상태 표시: 사용자에게 진행 상황 피드백.

구현
typescript
'use client';
// components/UserMenu.tsx
import { useSession } from 'next-auth/react';

export default function UserMenu() {
  const { data: session, status } = useSession();

  if (status === 'loading') return <Skeleton />;
  return session ? <ProfileMenu user={session.user} /> : <LoginButton />;
}
장점
사용자 경험 개선: 즉각적인 UI 반응.

세션 동기화: 탭 간 상태 일관성 유지.

5. NextAuth.js 설정: 확장성 확보
권장 구성
typescript
// auth.config.ts
import NextAuth from 'next-auth';
import Google from 'next-auth/providers/google';
import Credentials from 'next-auth/providers/credentials';

export const authOptions = {
  providers: [
    Google({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    }),
    Credentials({
      async authorize(credentials) {
        // 커스텀 로그인 로직 (예: 이메일/비밀번호)
      },
    }),
  ],
  callbacks: {
    async jwt({ token, user }) {
      if (user) token.role = user.role; // 역할 정보 추가
      return token;
    },
    async session({ session, token }) {
      session.user.role = token.role; // 클라이언트에 역할 전달
      return session;
    },
  },
};
보안 강화 설정
Cookie 옵션: Secure, HttpOnly, SameSite=Strict 적용.

세션 갱신: updateAge: 24 * 60 * 60 (24시간마다 갱신).

종합 비교 및 권장 조합
방법 사용 사례 보안 성능 구현 복잡도
미들웨어 전역 인증 검사 ⭐⭐⭐ ⭐⭐⭐ 낮음
서버 컴포넌트 역할 기반 접근 제어, SEO 최적화 ⭐⭐⭐ ⭐⭐⭐ 중간
API 라우트 백엔드 API 보호 ⭐⭐⭐ ⭐⭐ 중간
클라이언트 실시간 UI 업데이트 ⭐ ⭐⭐ 낮음
최적화 조합 전략
미들웨어로 기본 인증 검사 → 서버 컴포넌트에서 세부 권한 확인 → API 라우트에서 이중 검증.

클라이언트 컴포넌트는 보조적 UI 업데이트용으로만 사용.

NextAuth.js 설정: JWT + 세션 콜백으로 역할 정보 확장.

보안 추가 권장 사항
HTTPS 강제: next.config.js에서 HTTPS 리디렉션 설정.

환경 변수 관리: NEXTAUTH_SECRET 반드시 설정.

로그인 시도 제한: IP 기반 차단 또는 CAPTCHA 통합.

2FA(이중 인증): 중요한 기능에 대해 추가 인증 계층 적용.

이 조합을 통해 Next.js 애플리케이션의 인증 시스템을 견고하게 구축할 수 있으며, 확장성과 유지보수성도 함께 확보됩니다.

728x90

RunPod GPU 가격 비교 및 가성비 분석 (2025년 최신)

 

 

안녕하세요! 오늘은 AI 개발자머신러닝 연구자들이 많이 활용하는 RunPod 클라우드 GPU 서비스의 가격 비교가성비 분석에 대해 알아보겠습니다. 특히 VRAM 대비 가격을 중심으로 어떤 GPU가 여러분의 AI 워크로드에 가장 적합한지 살펴보겠습니다.

RunPod이란?

RunPod는 AI 개발, 머신러닝 훈련, 추론 등을 위한 GPU 클라우드 서비스로, 다양한 NVIDIA 및 AMD GPU를 온디맨드 방식으로 제공합니다. 특히 최신 GPU 모델을 경쟁력 있는 가격에 이용할 수 있어 프리랜서 개발자부터 스타트업, 연구 기관까지 널리 사용되고 있습니다.

VRAM 대비 가격 분석

AI 모델, 특히 대형 언어 모델(LLM)이미지 생성 모델을 실행할 때 가장 중요한 자원은 **VRAM(비디오 메모리)**입니다. 아래는 RunPod에서 제공하는 주요 GPU의 VRAM 대비 시간당 비용을 분석한 표입니다.

 

주요 GPU별 상세 분석

1. NVIDIA A40 (최고의 VRAM 가성비)

  • VRAM: 48GB GDDR6
  • 시간당 비용: $0.35 (할인가)
  • 1GB당 비용: $0.0073
  • 특징:
    • TensorCore 지원으로 딥러닝 성능 향상
    • High 가용성으로 안정적인 작업 가능
    • 9 vCPU50GB RAM으로 전처리 작업도 원활

A40은 VRAM 대비 가격이 가장 우수한 GPU로, 중대형 AI 모델을 다루는 데 적합합니다. 특히 LoRA 파인튜닝과 같은 메모리 집약적 작업에서 뛰어난 가성비를 보여줍니다.

2. AMD MI300X (최대 VRAM 용량)

  • VRAM: 192GB HBM3
  • 시간당 비용: $1.99 (할인가)
  • 1GB당 비용: $0.0104
  • 특징:
    • 압도적인 192GB VRAM 용량
    • 24 vCPU283GB RAM으로 강력한 성능
    • 낮은 가용성(Low)이 단점

MI300X는 초대형 언어 모델(LLM) 작업에 이상적인 선택입니다. Llama-70B, Claude, GPT-4급 모델을 양자화 없이 전체 정밀도로 로드할 수 있습니다.

3. NVIDIA L40 vs L4 비교

  • L40:
    • 48GB VRAM, $0.84/hr, $0.0175/GB/hr
    • Transformer 엔진 최적화로 어텐션 메커니즘 가속
    • 8 vCPU94GB RAM으로 복잡한 데이터 파이프라인 지원
  • L4:
    • 24GB VRAM, $0.37/hr, $0.0154/GB/hr
    • 저전력 설계로 추론에 최적화
    • 12 vCPU50GB RAM으로 경량 작업에 적합

L40은 훈련 및 고급 추론에, L4는 효율적인 배포 및 서빙에 더 적합합니다.

AI 워크로드별 최적 GPU 추천

대형 언어 모델(LLM) 작업

  • 전체 모델 로드: AMD MI300X (192GB) 또는 H200 SXM (141GB)
  • LoRA/QLoRA 파인튜닝: A40 (48GB) 또는 L40 (48GB)
  • 양자화 기반 추론: L4 (24GB) 또는 RTX A5000 (24GB)

이미지 생성 모델

  • 고해상도 생성: RTX A6000 (48GB) 또는 A40 (48GB)
  • 실시간 이미지 생성: RTX 4090 (24GB)
  • 효율적 배치 처리: RTX 4000 Ada (20GB)

멀티모달 AI 작업

  • 비전-언어 모델: L40 (48GB) 또는 A40 (48GB)
  • 오디오-텍스트 변환: RTX A5000 (24GB)
  • 비디오 처리: RTX 4090 (24GB) 또는 RTX A6000 (48GB)

경제적인 RunPod 사용 전략

1. 스팟 인스턴스 활용

RunPod스팟 인스턴스온디맨드 가격보다 15-40% 저렴합니다. 중단 허용 작업에는 항상 스팟 인스턴스를 활용하는 것이 경제적입니다.

2. 작업 특성에 맞는 GPU 선택

  • 훈련 작업: VRAM이 큰 A40, L40, MI300X
  • 추론 작업: 추론 최적화된 L4, RTX 4000 Ada
  • 개발 및 실험: 비용 효율적인 RTX A4000, RTX 3090

3. 모델 최적화 기법 적용

  • 양자화(Quantization): INT8/INT4 최적화로 VRAM 요구량 감소
  • 모델 프루닝(Pruning): 불필요한 가중치 제거
  • 모델 샤딩(Sharding): 여러 GPU에 모델 분산

VRAM과 RAM의 역할과 중요성

VRAM의 역할

  • 모델 가중치 저장
  • 어텐션 캐시KV 캐시 관리
  • 중간 활성화값 저장
  • 텐서 연산 가속

시스템 RAM의 역할

  • 데이터 전처리 및 배치 구성
  • 모델 로딩 준비
  • 결과 후처리 및 저장
  • VRAM 오버플로 시 임시 저장소

대형 AI 모델을 효율적으로 실행하려면 VRAMRAM 간의 균형이 중요합니다. RunPod의 모든 GPU는 적절한 비율의 RAM을 제공하지만, 특별한 작업에는 더 많은 RAM이 필요할 수 있습니다.

결론: 가성비 최고의 GPU는?

VRAM 대비 가격만 고려한다면 NVIDIA A40이 명확한 승자입니다. 하지만 워크로드 특성과 실제 성능을 고려하면:

  • 최고의 종합 가성비: NVIDIA A40
  • 대규모 LLM 작업용: AMD MI300X
  • 효율적인 추론용: NVIDIA L4
  • 중소규모 프로젝트용: RTX A4000

자신의 AI 워크로드 요구사항과 예산 제약을 고려하여 최적의 GPU를 선택하는 것이 중요합니다.

마지막 팁: RunPod 사용 시 비용 절감 방법

  1. 사용하지 않는 인스턴스는 즉시 중지하기
  2. 도커 이미지 최적화로 시작 시간 단축
  3. 체크포인트 저장으로 작업 연속성 확보
  4. 볼륨 스토리지 최적화로 스토리지 비용 절감
  5. 자동화 스크립트로 유휴 시간 최소화

여러분은 어떤 GPU를 사용하고 계신가요? 특별한 AI 프로젝트에 적합한 GPU에 대한 조언이 필요하시면 댓글로 남겨주세요! 다음 글에서는 RunPod vs Lambda Labs vs Vast.ai 가격 비교 분석을 진행할 예정입니다. 구독과 좋아요 부탁드립니다! 👍

 

728x90

롯데리아 치즈버거 먹고 헬스장에서 운동!
땀이 많이 났다
근력&유연성 운동하고 코딩하러 가야지

728x90

런닝머신, 힙어덕션
명륜진사갈비 가서 마음껏 먹고 죄책감에 칼로리 소모, 내전근 강화, 하체 근력 운동

728x90

천국의 계단! 6단계로 놓고 3분 정도 연속으로 타니 땀난다ㅠ

728x90

매일 헬스장가서 조금이라도 근력 운동, 유연성 운동 해야겠다!

728x90

Make.com를 활용하여 뉴스 요약 및 SNS 포스팅 자동화

https://www.gpters.org/nocode/post/make-comreul-hwalyonghayeo-nyuseu-yoyag-mic-sns-poseuting-jadonghwa-TuLKAi3I6HwgXkB

 

 

위와 같은 플로우를 설정해서 아래와 같은 다양한 기능들을 구현할 수 있다. 생성형 AI 및 프롬프트를 접목하여 더 다양한 것을 시도할 수 있다. 신기한 툴이 많이 나오는 것 같다! 

1. 기능

구글스프레드 시트에서 키워드 불러오기

키워드로 뉴스를 검색하기

Claude를 이용하여 뉴스 요약하기

DALL-E를 이용하여 관련 이미지 생성하기

요약된 뉴스를 메일 전송하기

뉴스 요약 내용 검토하기

확인된 뉴스 요약을 SNS(instagram, facebook)에 포스팅하기

 

읽어주셔서 감사합니다!!

728x90

2030년 세상은 어떤 모습일까?

 

 

2030년을 상상해보세요. 불과 10년 전만 해도 공상과학 영화에서나 볼 법한 일들이 우리의 일상이 되어버린 세상입니다. AI 비서와 대화하며 하루를 시작하고, 자율주행차를 타고 출근하는 모습이 더 이상 낯설지 않죠. 

이 글에서는 2030년의 모습을 생생하게 그려보려 합니다. 스마트홈과 AI의 보편화, 서비스의 자동화, 가상 인물의 보편화와 그에 따른 정보의 신뢰성 문제, 그리고 역설적으로 부활하는 오프라인 커뮤니티의 중요성까지. 또한 이러한 변화들이 우리의 노동 시장과 사회 구조에 어떤 영향을 미치는지도 고민해보았습니다.

미래는 언제나 불확실하지만, 그 속에서 기회와 도전을 발견하는 것은 우리의 몫입니다. 2030년의 세상이 어떤 모습일지, 그리고 그 속에서 우리는 어떻게 살아가게 될지, 함께 들여다보시죠. 이 여정이 여러분에게 미래에 대한 흥미로운 통찰을 제공하고, 다가올 변화에 대비할 수 있는 영감을 줄 수 있기를 바랍니다.

1. 스마트홈과 AI 비서의 보편화

a) 건강 관리: AI 비서가 사용자의 수면 패턴, 활동량, 식습관을 분석하여 맞춤형 건강 조언을 제공합니다. "오늘은 스트레스 지수가 높아 보이네요. 10분간 명상을 추천드립니다."

b) 에너지 효율: AI가 날씨와 사용자의 일정을 고려해 집의 온도와 조명을 자동으로 조절합니다. "오후 3시에 귀가 예정이시네요. 실내 온도를  미리 조절해 놓겠습니다."

c) 약자 돌봄: 노인이나 아이들의 활동을 모니터링하고 이상 징후를 감지합니다. "할머니께서 30분째 움직임이 없으십니다. 확인해 보시는 게 어떨까요?"

d) 사물인터넷 최적화를 위한 운영체제 출현: 콘센트 및 기기와 자연스럽게 연동되어 조절되는 신세대 인터페이스 개발. 전세계적으로 이 규격이 통일될 수도. 자동차, 휴대폰, 아파트, 컴퓨터 등의 기기들과 모두 자연스럽고 효율적으로 통신.

 

2. 서비스의 자동화

초개인화 서비스의 발달, 개인과 가정의 데이터를 활용한 인공지능 서비스들의 다수 출현.

a) 자동 식료품 보충: 냉장고가 내용물을 파악하고 부족한 식료품을 자동으로 주문합니다. "우유가 다 떨어졌네요. 내일 아침 배송으로 주문해 놓았습니다.", "내일 저녁에 돼지 목살을 구워먹고 싶어" -> 목살 등을 주문합니다.

b) 스마트 옷장: 날씨와 일정에 맞는 옷을 추천하고, 세탁이 필요한 옷을 자동으로 세탁 서비스에 보냅니다. "오늘 비 올 확률이 90%네요. 방수 자켓을 추천드립니다."

c) 자동 차량 정비: 차량이 자체 진단을 통해 정비가 필요한 부분을 감지하고 정비소와 예약을 잡습니다. "타이어 공기압이 낮습니다. 내일 오전 10시에 근처 정비소 예약을 해두었습니다."

3. 가상 인물과 정보의 신뢰성 문제

a) 가짜 리뷰, 가짜 뉴스 확대: 정보 검증 시스템의 중요성이 더욱 부각됩니다. 신뢰있는 정보인가에 대한 중요성 증가.

b) 유튜브 및 SNS: 실제 인물과 구분할 수 없는 가상 인물들로 영상 대부분을 차지.

4. 오프라인 커뮤니티의 부활

가상 현실 기술, 비대면 서비스와 자동화의 확산으로 인해 사람들은 점점 더 온라인에서 시간을 보내게 되었습니다. 그러나 역설적으로 이러한 상황이 오프라인 커뮤니티에서 취미과 경험을 공유하고자 하는 수요를 높이고 있습니다. 사람들은 직접적인 대면 접촉과 실제 경험을 갈망하게 되었고, 이는 새로운 형태의 오프라인 모임과 활동의 증가로 이어지고 있습니다.

a) 테크 프리 카페: 전자기기 사용이 금지된 공간이 인기를 끕니다. 사람들은 이곳에서 직접 대화를 나누고 아날로그 게임을 즐깁니다.

5. 노동 시장의 변화

제조업을 비롯한 여러 산업 분야에서 공장 자동화와 AI의 도입으로 인력 수요가 크게 줄어들었습니다. 이로 인해 부의 양극화가 심화되었고, 기본소득제도의 필요성이 대두되고 있습니다. 일부에서는 '인공지능세' 등의 새로운 과세 방안을 통해 이 문제를 해결하려는 움직임도 있습니다.

a) AI 교육 센터: 일자리를 잃은 노동자들을 위한 AI와 협업할 수 있는 기술 교육 센터가 정부 지원으로 설립됩니다.

b) 사회 공헌 포인트 제도: 기업들이 자동화로 절감한 비용의 일부를 사회에 환원하는 제도가 도입되어, 시민들의 자원봉사 활동과 연계됩니다. 시민들은 봉사활동을 통해 소득을 얻거나 생계를 유지합니다.

c) 4일제 근무의 보편화: AI와 자동화로 인한 생산성 향상으로, 많은 기업들이 4일 근무제를 도입합니다. 이에 따라 여가 산업이 크게 성장합니다.

 

 

728x90

성취 = 기회 + 성공률 (유튜브 시청 후기)

 

 

https://www.youtube.com/watch?v=ilgwsmeBN5w

 

 

새로운 시도를 안하려는 이유

원시 시절에 학습된 DNA (수영 잘해서 물고기 먹고 사는 사람이, 산에 갔다가 죽었다 -> 유전자에 학습됨)

-> 원시 시대 때는 이 전략이 유리했겠지만, 기회가 성취를 이루는 데 중요한 현대에서는 오히려 도전하지 않는 것이 안좋은 전략이 될 수 있다. 각인된 본능에 거슬러서 기회를 많이 만들어야 겠다는 생각이 들었다.

 

얕고 넓게 알기

일반인보다는 잘하는 수준이지만, 전문가보다는 낮은 수준.

팔방미인(모든 분야에서의 전문가)까지는 아니더라도 팔방훈남, 팔방훈녀(일반인 보다는 잘하는 수준) 되기

-> 내가 좋아하는 것과 잘하는 것과 잘하고 싶은 분야을 주말에 리스트업 해야겠다.

영리한 적극성으로 살기

모르는 분야에서 잘하는 사람 것을 그대로 배껴오기

새로운 시도를 하고 여러 가지 경험들, 다양한 역량들이 모이면 시너지가 난다. (글쓰기, 투자, 운동 등)

-> 잘하는 사람을 어디에서 찾을까? 유튜브, 강의(udemy), 독서?

 

 

 

728x90

sqlc 사용기: Go + PostgreSQL, SQL 기능을 하는 함수 생성

 

 

 

 

1. Intro

sqlc라 최고라는 말을 듣고 적용해보았다!.. 이 도구는 SQL 스키마와 쿼리를 기반으로 타입 안전한 Go 코드를 생성해주어, 안정성과 개발 생산성을 높여준다고 합니다. 이번 글에서는 sqlc를 사용하여 PostgreSQL 데이터베이스와 상호작용하는 방법에 대해 살펴보겠습니다.

2. sqlc 설정

sqlc를 사용하기 위해서는 먼저 sqlc.yaml 설정 파일을 생성해야 합니다. 이 파일에는 데이터베이스 엔진, 쿼리 파일, 스키마 파일, 그리고 생성된 Go 코드의 출력 위치 등을 지정합니다.

version: "2"
cloud:
  project: "xxxxxxxxxxx"
sql:
  - engine: "postgresql"
    queries: "query.sql"
    schema: "schema.sql"
    database:
      uri: "postgres://postgres:xxxxxxxx@xxx-rds.xxxxxxx.ap-xxxxxxxx-2.rds.amazonaws.com:5432/postgres"
    gen:
      go:
        package: "db"
        out: "db"
        sql_package: "pgx/v5"

 

 

3. 스키마 정의

schema.sql 파일에 데이터베이스 테이블 구조를 정의합니다. 예를 들어, authors 테이블을 다음과 같이 정의할 수 있습니다

CREATE TABLE authors (
  id   BIGSERIAL PRIMARY KEY,
  name text      NOT NULL,
  bio  text
);

테이블 정보를 담은 DDL을 실행해 줍니다. 자동으로 마이그레이션을 해주고, 마이그레이션 파일을 저장해주는 라이브러리가 있다고 하는데, 다음에 적용해볼 예정입니다.

 

4. 쿼리 작성

query.sql 파일에 필요한 데이터베이스 쿼리를 작성합니다. sqlc는 이 쿼리들을 분석하여 Go 함수로 변환합니다.

-- name: GetAuthor :one
SELECT * FROM authors
WHERE id = $1 LIMIT 1;

-- name: ListAuthors :many
SELECT * FROM authors
ORDER BY name;

-- name: CreateAuthor :one
INSERT INTO authors (
  name, bio
) VALUES (
  $1, $2
)
RETURNING *;

-- name: UpdateAuthor :exec
UPDATE authors
  set name = $2,
  bio = $3
WHERE id = $1;

-- name: DeleteAuthor :exec
DELETE FROM authors
WHERE id = $1;

 

 

5. 코드 생성

sqlc 설정이 완료되면, 다음 명령어를 실행하여 Go 코드를 생성합니다

sqlc generate

이 명령은 db/ 폴더 내에 query.sql.go, models.go, db.go 파일을 생성합니다. 이 파일들에는 데이터베이스 SQL 구문을 실행하기 위한  함수들이 포함되어 있습니다.


6. 생성된 코드 사용

생성된 코드를 사용하여 데이터베이스 작업을 수행할 수 있습니다. 예를 들어

// Code generated by sqlc. DO NOT EDIT.
// versions:
//   sqlc v1.26.0
// source: query.sql

package db

import (
	"context"

	"github.com/jackc/pgx/v5/pgtype"
)

const createAuthor = `-- name: CreateAuthor :one
INSERT INTO authors (
  name, bio
) VALUES (
  $1, $2
)
RETURNING id, name, bio
`

type CreateAuthorParams struct {
	Name string
	Bio  pgtype.Text
}

func (q *Queries) CreateAuthor(ctx context.Context, arg CreateAuthorParams) (Author, error) {
	row := q.db.QueryRow(ctx, createAuthor, arg.Name, arg.Bio)
	var i Author
	err := row.Scan(&i.ID, &i.Name, &i.Bio)
	return i, err
}

const deleteAuthor = `-- name: DeleteAuthor :exec
DELETE FROM authors
WHERE id = $1
`

func (q *Queries) DeleteAuthor(ctx context.Context, id int64) error {
	_, err := q.db.Exec(ctx, deleteAuthor, id)
	return err
}

const getAuthor = `-- name: GetAuthor :one
SELECT id, name, bio FROM authors
WHERE id = $1 LIMIT 1
`

func (q *Queries) GetAuthor(ctx context.Context, id int64) (Author, error) {
	row := q.db.QueryRow(ctx, getAuthor, id)
	var i Author
	err := row.Scan(&i.ID, &i.Name, &i.Bio)
	return i, err
}

const listAuthors = `-- name: ListAuthors :many
SELECT id, name, bio FROM authors
ORDER BY name
`

func (q *Queries) ListAuthors(ctx context.Context) ([]Author, error) {
	rows, err := q.db.Query(ctx, listAuthors)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var items []Author
	for rows.Next() {
		var i Author
		if err := rows.Scan(&i.ID, &i.Name, &i.Bio); err != nil {
			return nil, err
		}
		items = append(items, i)
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return items, nil
}

const updateAuthor = `-- name: UpdateAuthor :exec
UPDATE authors
  set name = $2,
  bio = $3
WHERE id = $1
`

type UpdateAuthorParams struct {
	ID   int64
	Name string
	Bio  pgtype.Text
}

func (q *Queries) UpdateAuthor(ctx context.Context, arg UpdateAuthorParams) error {
	_, err := q.db.Exec(ctx, updateAuthor, arg.ID, arg.Name, arg.Bio)
	return err
}
import (
    "context"
    "your-project/db"
)

func main() {
    // 데이터베이스 연결 설정
    conn, err := pgx.Connect(context.Background(), "your-database-url")
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close(context.Background())

    queries := db.New(conn)

    // 새 저자 생성
    author, err := queries.CreateAuthor(context.Background(), db.CreateAuthorParams{
        Name: "John Doe",
        Bio: pgtype.Text{String: "A prolific writer", Valid: true},
    })
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Created author: %v\n", author)

    // 저자 목록 조회
    authors, err := queries.ListAuthors(context.Background())
    if err != nil {
        log.Fatal(err)
    }
    for _, a := range authors {
        fmt.Printf("Author: %s\n", a.Name)
    }
}

sqlc를 사용하면 데이터베이스 스키마, SQL 쿼리와 Go 코드 사이의 불일치를 줄이고, type-safe을 보장하며, 개발 생산성을 향상시킬 수 있습니다. 또한 데이터베이스 스키마 변경 시 자동으로 코드를 업데이트할 수 있어 유지보수가 용이하다고 합니다.

결론적으로, sqlc는 Go와 PostgreSQL을 함께 사용하는 프로젝트에서 강력한 도구가 될 수 있습니다. 타입 안전한 데이터베이스 액세스 코드를 자동으로 생성함으로써 개발자는 비즈니스 로직에 더 집중할 수 있게 되며, 데이터베이스 관련 오류를 크게 줄일 수 있는 장점이 있다고 합니다.

읽어주셔서 감사합니다 ☺️

 

 

 

728x90

ssh config, shell script로 배포 시간 1/10으로 단축하기

 

 

0. Intro

생성형 AI로 일적으로나 생활적으로 도움을 많이 받고 있는 요즘, 업무 차원에서 개발 속도를 어떻게 하면 더욱 향상시킬까 하는 고민이 많은데요.

하지만 때로는 우리가 선택한 개발 및 배포 환경이 개발 속도를 방해하곤 합니다. 저 역시 최근에 그런 경험을 했습니다.

1. 기존 배포 방식의 단점

이전에 저는 docker + GitHub Actions + ECS + ECR 조합으로 배포 파이프라인을 구축했습니다. 이 방식은 코드를 푸시하는 순간 자동으로 배포가 이루어지고 github에서 종합적으로 관리할 수 있다는 장점이 있었죠. 하지만 실제로 develop 브랜치에 푸시되고 github action으로 docker 이미지가 빌드되고, ECS의 Task가 생성될 때까지, 즉, 서버에 변경사항이 반영되기까지 무려 5분이나 걸렸습니다.

 

처음에는 '자동화됐으니 괜찮아'라고 생각했지만, 점점 이 긴 대기 시간(코드를 변경하고 서버에 반영되는 시간)이 답답하게 느껴졌습니다. 특히 릴리즈 환경이 아니라 소규모로 개발하는 경우에는 이 5분의 대기 시간이 개발 속도에 상당한 영향을 미쳤습니다. 작은 변경사항 하나 를 커밋하고 테스트하려고 해도 커피 한 잔 마시고 올 시간이 있었으니까요.

물론 여러 명이 협업할 때는 이런 자동화된 파이프라인이 분명한 장점이 있습니다. 하지만 개인 프로젝트나 빠른 개발이 필요한 상황에서는 오히려 족쇄가 되는 느낌이었습니다.

2. 새로운 배포 방식: ssh config와 shell script의 활용

이러한 문제를 해결하기 위해 저는 좀 더 단순하면서도 빠른 배포 방식을 고안했습니다. 바로 ssh config와 shell script를 활용한 방법입니다.

새로운 방식의 흐름은 다음과 같습니다:

1. 로컬 환경에서 Docker 이미지를 빌드합니다. (I love M2 Pro 맥북❤️)

2. 빌드된 이미지를 Docker hub에 푸시합니다.

3. ssh를 통해 AWS EC2 서버에 원격으로 접속합니다.

4. 서버에서 최신 Docker 이미지를 풀(pull)받습니다.

5. 풀받은 이미지로 컨테이너를 실행합니다.

이 모든 과정을 shell script로 자동화했더니, 놀랍게도 전체 배포 시간이 28초 정도로 줄어들었습니다! 기존 방식의 약 5분(300초)에서 1/10 수준으로 단축된 것이죠.

28초 ㅁㅊㄷ..!

 

3. 결론

개발 환경과 배포 방식은 항상 트레이드오프가 있습니다. 팀 규모, 프로젝트의 성격, 개발 단계 등에 따라 최적의 방식이 달라질 수 있습니다.
이번 경험을 통해 저는 상황에 맞는 유연한 접근이 중요하다는 것을 깨달았습니다. 때로는 '최신' 기술 스택보다 단순하지만 효율적인 방식이 더 나을 수 있다는 것도 알게 되었고요.

여러분도 현재 사용 중인 개발 및 배포 프로세스를 한 번 점검해보는 것은 어떨까요? 어쩌면 작은 변화로 큰 효율을 얻을 수 있을지도 모릅니다.

읽어주셔서 감사합니다 🙇‍♂️

 

 

728x90

배포 자동화: Go + Docker + ECS + Fargate + ECR + Github Action

 

 

1. Intro

인프라에 대해 공부해보다가 Go + Docker + ECS + Fargate + ECR + Github Action으로 CI/CD를 구성했다는 블로그 글을 보고, 따라해 보았다.

 

2. Dockerfile 만들기

Dockerfile은 Docker 이미지를 생성하기 위한 설정 파일입니다. Go + gin backend를 위한 Dockerfile을 만들어 보겠습니다.

FROM golang:alpine AS builder

ENV GO111MODULE=on \
    CGO_ENABLED=0 \
    GOOS=linux \
    GOARCH=amd64

WORKDIR /build

COPY go.mod go.sum main.go ./

RUN go mod download

RUN go build -o main .

WORKDIR /dist

RUN cp /build/main .

FROM scratch

COPY --from=builder /dist/main .

ENTRYPOINT ["/main"]

 

 

3. ECS 만들기 (Fargate vs EC2)

Amazon ECS(Elastic Container Service)는 컨테이너화된 애플리케이션을 쉽게 실행하고 관리할 수 있는 완전 관리형 컨테이너 오케스트레이션 서비스입니다. ECS를 사용할 때 두 가지 주요 실행 모드가 있습니다 (Fargate, EC2). 생성할 때 두 개 중 하나를 선택할 수 있습니다. 저는 Fargate를 선택했습니다.

Fargate

서버리스 옵션으로, 인프라 관리 없이 컨테이너를 실행할 수 있습니다. 확장성이 뛰어나고 관리가 쉽습니다.

EC2

사용자가 직접 EC2 인스턴스를 관리하며, 더 많은 제어와 커스터마이징이 가능합니다.

예전에는 EC2 + Code Deploy 조합으로 배포 인프라 구성을 하곤 했는데, 이번에는 Fargate를 시도해보고자 합니다.

Fargate는 인프라 관리 부담을 줄이고 싶은 경우에 적합하며, EC2는 더 세밀한 제어가 필요한 경우에 선택한다고 합니다.

클러스터 (Cluster) 개념

ECS의 최상위 리소스 그룹입니다.
여러 개의 EC2 인스턴스 또는 Fargate 작업을 논리적으로 그룹화합니다. (저는 Fargate를 사용했습니다.)
한 클러스터 내에서 여러 서비스와 태스크를 실행할 수 있습니다.

태스크 (Task) 개념

애플리케이션의 최소 배포 단위입니다.
하나 이상의 컨테이너로 구성됩니다.
태스크 정의(Task Definition)를 통해 실행할 컨테이너, 사용할 리소스 등을 정의합니다.



서비스 (Service)

지정된 수의 태스크를 클러스터에서 동시에 실행하고 관리합니다.
태스크가 중지되면 자동으로 새 태스크를 시작하여 지정된 개수를 유지합니다. (저는 1개를 지정해서 최대 1개만 실행됩니다.)
로드 밸런서와 연결하여 트래픽을 분산할 수 있다고 합니다. (요금 주의)



컨테이너 (Container)

Docker 이미지를 기반으로 실행되는 격리된 실행 환경입니다. (ECR에 push하고 태그로 관리됩니다)
go + gin 으로 구성된 프로젝트를 Docker로 빌드했습니다. 
태스크 내에서 하나 이상의 컨테이너가 함께 실행될 수 있습니다.

 

4. 인바운드 규칙 편집

8080 포트 열기 (인바운드 규칙 편집)

ECS 태스크가 실행되는 Fargate 태스크에 연결된 보안 그룹의 인바운드 규칙을 편집해야 합니다.
ECS -> 클러스터 -> 서비스 -> 보안 그룹으로 이동합니다.
해당 보안 그룹을 선택하고 "인바운드 규칙 편집"을 클릭합니다.

유형: 사용자 지정 TCP
포트 범위: 8080
소스: 필요에 따라 특정 IP 범위 또는 모든 트래픽(0.0.0.0/0)을 선택

저는 8080포트를 열었으므로, 8080포트를 외부에서 접근 가능하도록 개방했습니다.



이렇게 설정하면 외부에서 8080 포트를 통해 ECS에서 실행 중인 go backend app에 접근할 수 있게 됩니다. 단, 보안을 위해 가능한 한 접근을 제한적으로 설정하는 것이 좋습니다.
또한, 태스크 정의에서도 컨테이너 포트 매핑을 설정해야 합니다. 태스크 정의의 컨테이너 설정에서 8080 포트를 호스트 포트와 매핑해야 합니다.

 

4. ECR 만들기 (Docker 이미지 저장소)

Amazon ECR(Elastic Container Registry)은 Docker 컨테이너 이미지를 안전하게 저장, 관리 및 배포할 수 있는 완전 관리형 Docker 컨테이너 레지스트리입니다. ECR을 사용하면 프라이빗 레포지토리를 생성하여 Docker 이미지를 저장하고, ECS와 쉽게 통합할 수 있습니다.

 

5. Github Action (develop branch에 머지 시 워크플로우 실행)

develop 브랜치에 머지가 되었을 때 (이벤트 발생), Docker 이미지가 빌드되고 ECR에 push(업로드)된 다음에, 해당 도커 이미지를 ECS에서 실행되게 할 것입니다. (8080포트로 접근 가능하게)

GitHub Actions를 사용하면 이러한 CI/CD 파이프라인을 구축할 수 있습니다. develop 브랜치에 머지될 때 자동으로 빌드, 테스트, 배포 과정을 실행하도록 설정할 수 있습니다.

aws.yml 코드

name: Deploy to Amazon ECS

on:
  push:
    branches: [ "develop" ]

env:
  AWS_REGION: ap-northeast-2                   # set this to your preferred AWS region, e.g. us-west-1
  ECR_REPOSITORY: fye-backend-repository           # set this to your Amazon ECR repository name
  ECS_SERVICE: fye-backend-service                 # set this to your Amazon ECS service name
  ECS_CLUSTER: fye-cluster                 # set this to your Amazon ECS cluster name
  ECS_TASK_DEFINITION: ./fye-task.json # set this to the path to your Amazon ECS task definition
                                               # file, e.g. .aws/task-definition.json
  CONTAINER_NAME: fye-backend-container           # set this to the name of the container in the
                                               # containerDefinitions section of your task definition

permissions:
  contents: read

jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    environment: production

    steps:
    - name: Checkout
      uses: actions/checkout@v4

    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v1
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: ${{ env.AWS_REGION }}

    - name: Login to Amazon ECR
      id: login-ecr
      uses: aws-actions/amazon-ecr-login@v1

    - name: Build, tag, and push image to Amazon ECR
      id: build-image
      env:
        ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
        IMAGE_TAG: ${{ github.sha }}
      run: |
        # Build a docker container and
        # push it to ECR so that it can
        # be deployed to ECS.
        docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
        docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
        echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT
    - name: Fill in the new image ID in the Amazon ECS task definition
      id: task-def
      uses: aws-actions/amazon-ecs-render-task-definition@v1
      with:
        task-definition: ${{ env.ECS_TASK_DEFINITION }}
        container-name: ${{ env.CONTAINER_NAME }}
        image: ${{ steps.build-image.outputs.image }}

    - name: Deploy Amazon ECS task definition
      uses: aws-actions/amazon-ecs-deploy-task-definition@v1
      with:
        task-definition: ${{ steps.task-def.outputs.task-definition }}
        service: ${{ env.ECS_SERVICE }}
        cluster: ${{ env.ECS_CLUSTER }}
        wait-for-service-stability: true

상단에 있는 env: 부분에서 지역과 ecs, ecr 관련 정보들을 넣습니다. json은 태스크 탭에서 다운로드할 수 있습니다. 그리고 iam을 생성해서 github secrets에 access key와 secret key를 등록하여 github action이 aws의 다양한 서비스를 조작할 수 있게 설정합니다. 저는 iam에 아래의 6개 권한을 추가했습니다. 그리고 .csv로 추출하여 github의 secret에 키를 등록했습니다.

6개 권한 추가
인프라 구성 성공!, 수많은 시도들..

 

6. 마무리 및 소감

No Docker + Code Deploy로 했을 때는 1~2분이면 서버 배포가 끝났는데, Docker + ECS로 해보니 생각보다 오래 걸려서 당황했다..

Docker Build 하는데만 48s, Deploy ECS Task Definition 하는 데만 4m 16s가 걸렸다. 총 5분.. 

다수가 협업할 때는 장점이 충분히 있을 것 같지만, 1인이나 소규모로 할 때는 No Docker + Code Deploy를 사용하는 게 속도면에서 좋을 것 같다.

728x90

변산 가족여행 2일차 - 선유도, 채석강, 탁구, 코딩


안녕하세요! 오늘은 변산 가족여행 2일차 이야기를 들려드리려고 합니다! 

 

1. 오전 - 황태국과  선유도


아침은 든든한 황태국으로 시작했어요. 영양 가득한 식사로 하루를 열고, 우리 가족은 숙소에서 30분 거리인 선유도로 향했습니다. 선유도의 아름다운 자연 경관은 정말 감탄이 절로 나왔어요. 자연 그대로의 소금기를 담은 공기와 피톤치드를 마시며 몸과 마음이 회복되는 기분이었죠. 선유도에서는 여유롭게 드라이브를 즐기며 곳곳에서 사진을 찍었어요. 유람선을 탈까 고민했지만, 결국 차로 돌아다니는 것으로 만족했답니다. 차 안에서 보는 풍경도 충분히 아름다웠거든요.

 

2. 오후: 맛있는 생선과 리트리버

점심 시간이 되어 격포해수욕장과 채석강으로 향했어요. 더운 날씨에 시원한 슬러시로 더위를 식히며 걸어다녔어요. 점심은 근처 식당에서 고등어와 가자미 생선 정식을 먹었는데, 정말 맛있었어요.

식사 후 근처 카페에 들렀는데, 카페 안에 진짜 몸집이 큰 리트리버가 낮잠을 자고 있었거든요! 너무 놀라서 안에서 먹으려다가 가지고 나가기로 결정했답니다!

3. 저녁: 탁구 & 김치찌개

숙소로 돌아와 잠깐 낮잠을 자고 있는데, 엄마가 갑자기 탁구를 치자고 하셨어요. 숙소에 탁구장이 있어서 탁구 대결로 오후 시간을 보냈죠.

저녁으로는 직접 끓인 김치찌개와 배달의 민족으로 주문한 BHC 치킨을 먹었어요. 여행 중이지만 집에서 먹는 것 같은 편안함이 좋았답니다. 

4. 예상치 못한 업무

여행 중에도 일을 했다고요? 네, 맞아요. 원래 이번 달 초까지 해야 했었던 중요한 업무가 7월 말까지로 연장되었는데, 오늘이 월요일인지라  외부에서 업무 요청이 여러 번 왔었어요. 휴가라 일을 해야하나 싶었지만, 이미 오늘 놀건 다 놀았고 회사에서 중요한 프로젝트니, 개발 & 배포하고 저녁에는 숙소에서 업무에 집중했네요. 다행히 성공적으로 마무리 했답니다! 하지만 그래도 오늘 충분히 즐거운 여행이었어요. 일과 삶의 균형을 찾는 것, 어렵지만 중요한 것 같아요. 내일은 마지막 날이니 적극적으로 숙소 주변을 돌아다녀봐야겠어요. 내일은 3일차 이야기로 찾아뵐게요. 다들 좋은 밤 되세요! 

 

 

728x90

변산 가족여행: 2박 3일 여행 후기 - 1일차

https://naver.me/GAi6fxpB

 

안녕하세요, 오늘은 가족들과 함께 떠난 변산 여행 2박 3일 여행의 첫날 후기를 소개해드리려고 합니다. (feat. NH농협생명 변산수련원)

1. 오전 - 금산 출발

아침 일찍 금산을 출발했습니다. 가는 길에 눈에 띄는 점이 있었어요. 도로 위에 흙이 쌓여있고, 인삼농사 막이 무너져 있는 모습이 보였습니다. 포크레인도 유난히 많이 보였죠.

궁금해서 인터넷으로 뉴스를 찾아보니, 최근 집중호우로 인해 금산 지역에 큰 피해가 있었다고 합니다. 공공시설 354건, 사유시설 350건의 피해가 접수되었고, 총 피해액이 357억 원에 달한다고 하네요. 우리가 출발할 때 보았던 모습들이 바로 이 피해의 흔적이었던 거죠.

2. 점심 - 변산 도착

점심 무렵 변산에 도착했습니다. 배가 고파 먼저 근처 맛집인 군산식당으로 향했어요. 이곳에서 꽃게탕과 백합찜을 주문했습니다.
꽃게탕은 처음 먹어보는 메뉴였는데, 정말 담백한 맛이 맛있었어요. 사장님 말씀으로는 담백한 맛을 유지하기 위해 오전에 미리 한 번 끓여놓은 거라고 하더라고요. 백합찜도 처음 먹어봤는데, 속살이 정말 알찼습니다.

3. 오후 - 체크인과 내소사 방문

식사 후 NH농협생명 변산수련원에 체크인했습니다. 우리 일행은 두 개의 방을 잡았어요. 시설이 좋았고, 바로 앞에 바다가 보이는 전망도 정말 좋았습니다.

잠깐의 낮잠 후, 우리는 내소사로 향했습니다. 내소사는 드라마 '대장금' 촬영지로도 유명하다고 하더라고요. 절에 들어서자마자 피톤치드가 충전되는 기분이였어요. 마음이 편안해지는 기분이었습니다.
다만 일요일 오후라 그랬는지 주변 상가들은 대부분 문을 닫았어요. 오디 음료? 파는 곳이 몇개 있더라고요. (오디 농사를 짓나?)

 

이렇게 첫날을 마무리했습니다. 내일은 어떤 즐거움이 기다리고 있을지 기대가 되네요!

읽어주셔서 감사합니다 

728x90

Golang으로 대량 API 요청 처리: Excel 파일에서 데이터 읽기부터 병렬 처리까지

 

안녕하세요, 오늘은 Go 언어를 사용하여 Excel 파일에서 데이터를 읽어와 대량의 API 요청을 처리하는 방법에 대해 알아보겠습니다. 이 프로그램은 입력할 Excel 파일에서 데이터를 읽어와 body에 넣어서 API에 요청을 보내고, 응답을 파일에 저장하는 과정을 수행합니다. 

1. 과정

Excel 파일에서 데이터 추출하기 (API request body에 넣을 값들)

API 요청 (병렬 처리를 위한 고루틴 사용 - WaitGroup)

응답 데이터 정렬 by index

결과를 파일로 저장 (csv)

로깅 (응답 데이터, 응답 시간 로그 파일 생성)


2. Excel 파일에서 원하는 데이터 추출하기

먼저, Excel 파일에서 데이터를 읽어오는 코드를 만듭니다.

package main

import (
	"encoding/csv"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"strings"
	"time"

	"github.com/tealeg/xlsx"
)

const (
	FILE_PATH      = "./sample.xlsx"
	SHEET_INDEX    = 1
	COLUMN         = 'F'
	START_ROW      = 2
	END_ROW        = 186
	PREVIEW_LENGTH = 500
)

func currentDateString() string {
	return time.Now().Format("20060102")
}

func readExcelColumn(filePath string, column rune, startRow, endRow int) ([]string, error) {
	xlFile, err := xlsx.OpenFile(filePath)
	if err != nil {
		return nil, fmt.Errorf("error: file '%s' not found", filePath)
	}

	if SHEET_INDEX >= len(xlFile.Sheets) {
		return nil, fmt.Errorf("error: sheet index out of range")
	}

	sheet := xlFile.Sheets[SHEET_INDEX]
	columnIndex := int(column - 'A')
	var columnData []string

	for i := startRow - 1; i < endRow; i++ {
		if i >= len(sheet.Rows) {
			break
		}
		cell := sheet.Rows[i].Cells[columnIndex]
		columnData = append(columnData, cell.String())
	}

	return columnData, nil
}

func processCellData(cellData string) string {
	parts := strings.Split(cellData, "\n")
	if len(parts) > 1 {
		return strings.TrimSpace(parts[1])
	}
	return strings.TrimSpace(parts[0])
}

func processColumnData(columnData []string) []string {
	var processedData []string
	for _, cell := range columnData {
		processedData = append(processedData, processCellData(cell))
	}
	return processedData
}

func saveToCSV(data []string, outputFile string) error {
	file, err := os.Create(outputFile)
	if err != nil {
		return fmt.Errorf("error writing to file: %v", err)
	}
	defer file.Close()

	writer := csv.NewWriter(file)
	defer writer.Flush()

	for _, item := range data {
		if err := writer.Write([]string{item}); err != nil {
			return fmt.Errorf("error writing to file: %v", err)
		}
	}

	fmt.Printf("Data has been saved to %s\n", outputFile)
	return nil
}

func previewFile(filePath string, length int) error {
	data, err := ioutil.ReadFile(filePath)
	if err != nil {
		return fmt.Errorf("error reading file for preview: %v", err)
	}

	fmt.Println("Preview of saved data:")
	fmt.Println(string(data)[:length])
	return nil
}

func main() {
	currentDate := currentDateString()
	outputFile := fmt.Sprintf("./body_%s.csv", currentDate)

	columnData, err := readExcelColumn(FILE_PATH, COLUMN, START_ROW, END_ROW)
	if err != nil {
		log.Fatalf("Failed to read Excel column: %v", err)
	}

	if len(columnData) == 0 {
		log.Println("No data to process. Check the Excel file and column specification.")
		return
	}

	processedData := processColumnData(columnData)

	if err := saveToCSV(processedData, outputFile); err != nil {
		log.Fatalf("Failed to save to CSV: %v", err)
	}

	if err := previewFile(outputFile, PREVIEW_LENGTH); err != nil {
		log.Fatalf("Failed to preview file: %v", err)
	}
}

 

3. 읽어온 데이터를 바탕으로 다수의 API 요청 보내기

WaitGroup을 사용해서 동시성을 활용해 API를 병렬적으로 실행합니다.

동시 작업 추적

WaitGroup은 완료되어야 할 고루틴(goroutine)의 수를 추적합니다. 이는 여러 고루틴이 병렬로 실행될 때 유용합니다.

작업 완료 대기

Main Go routine 이 모든 작업이 완료될 때까지 기다릴 수 있게 해줍니다. 이를 통해 모든 병렬 작업이 끝날 때까지 프로그램이 종료되지 않도록 합니다.

카운터 메커니즘

WaitGroup은 내부적으로 카운터를 사용합니다. 이 카운터는 다음 메서드들로 조작됩니다:

- Add(delta int): 대기해야 할 고루틴의 수를 증가시킵니다.
- Done(): 고루틴 하나가 완료되었음을 알립니다 (내부적으로 Add(-1)과 동일).
- Wait(): 카운터가 0이 될 때까지 블록합니다.

그리고 1번 요청을 먼저 했다고 해도 30번째 요청의 응답이 먼저 들어올 수 있으므로 index를 지정해서 모든 요청이 완료된 순간, index를 기준으로 오름차순 정렬을 하게 했습니다.

이로써, 동시성 프로그래밍을 활용하여 30개의 API 요청 시 응답시간을 270s -> 9s로 (1개 요청 시 보통 9s) 시간을 단축하였습니다.

package main

import (
	"bytes"
	"encoding/csv"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"sort"
	"strconv"
	"sync"
	"time"

	"go.uber.org/zap"

	"go.uber.org/zap/zapcore"
)

// 상수 정의
const (
	INPUT_FILE        = "body_20240714.csv"          // 입력 CSV 파일 이름
	API_URL           = "http://localhost:8000/api/v1/xxxx"   // API URL
	ASSISTANT_CONTENT = "" // 
	DATE_FORMAT       = "20240712"                            // 날짜 형식
	OUTPUT_CSV_FILE   = "response_20240712.csv"          // 출력 CSV 파일 이름
)

// CSV 파일 읽기 함수
func readCSV(filename string, logger *zap.Logger) ([]string, error) {

	logger.Info("Reading CSV file", zap.String("filename", filename)) // CSV 파일 읽기 시작 로그
	file, err := os.Open(filename)
	if err != nil {
		return nil, fmt.Errorf("Error opening file: %w", err) // 파일 열기 오류 시 에러 반환
	}
	defer file.Close()

	reader := csv.NewReader(file)
	var data []string
	for {
		record, err := reader.Read()
		if err != nil {
			break // 더 이상 읽을 레코드가 없으면 종료
		}
		if len(record) > 0 {
			data = append(data, record[0]) // CSV 레코드의 첫 번째 필드만 저장
		}
	}
	logger.Info("Successfully read records from CSV file", zap.Int("records", len(data))) // 성공적으로 읽은 레코드 수 로그
	return data, nil
}

// API 요청 함수
func makeAPIRequest(userContent string, wg *sync.WaitGroup, results chan<- [2]string, index int, logger *zap.Logger) {
	defer wg.Done() // 작업이 완료되면 WaitGroup의 작업 카운터를 줄임

	logger.Info("Making API request", zap.Int("index", index), zap.String("userContent", userContent)) // API 요청 시작 로그
	startTime := time.Now()                                                                            // 요청 시작 시간 기록

	requestData := []map[string]string{
		// 원하는 데이터 요청 형식
	}
	jsonData, err := json.Marshal(requestData)
	if err != nil {
		results <- [2]string{fmt.Sprintf("Error marshalling JSON: %v", err), fmt.Sprintf("%d", index)}
		logger.Error("Error marshalling JSON", zap.Int("index", index), zap.Error(err)) // JSON 변환 오류 로그
		return
	}

	req, err := http.NewRequest("POST", API_URL, bytes.NewBuffer(jsonData))
	if err != nil {
		results <- [2]string{fmt.Sprintf("Error creating request: %v", err), fmt.Sprintf("%d", index)}
		logger.Error("Error creating request", zap.Int("index", index), zap.Error(err)) // HTTP 요청 생성 오류 로그
		return
	}
	req.Header.Set("Content-Type", "application/json") // 요청 헤더 설정

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		results <- [2]string{fmt.Sprintf("Error making API request: %v", err), fmt.Sprintf("%d", index)}
		logger.Error("Error making API request", zap.Int("index", index), zap.Error(err)) // API 요청 오류 로그
		return
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		results <- [2]string{fmt.Sprintf("Error reading response: %v", err), fmt.Sprintf("%d", index)}
		logger.Error("Error reading response", zap.Int("index", index), zap.Error(err)) // 응답 읽기 오류 로그
		return
	}

	var response map[string]interface{}
	if err := json.Unmarshal(body, &response); err != nil {
		results <- [2]string{fmt.Sprintf("Invalid response format: %s", string(body)), fmt.Sprintf("%d", index)}
		logger.Error("Invalid response format", zap.Int("index", index), zap.String("body", string(body))) // 응답 형식 오류 로그
		return
	}

	if output, ok := response["output"].(string); ok {
		results <- [2]string{output, fmt.Sprintf("%d", index)}
		duration := time.Since(startTime)                                                                                                   // 요청 소요 시간 계산
		logger.Info("Received valid response", zap.Int("index", index), zap.String("response", output), zap.Duration("duration", duration)) // 유효한 응답 로그 및 소요 시간 기록
	} else {
		results <- [2]string{"No output field in response", fmt.Sprintf("%d", index)}
		logger.Warn("No output field in response", zap.Int("index", index)) // 응답에 output 필드가 없음 로그
	}
}

// CSV 파일에 데이터 저장 함수
func saveToCSV(filename string, data [][2]string, logger *zap.Logger) error {
	logger.Info("Saving data to CSV file", zap.String("filename", filename)) // CSV 파일 저장 시작 로그
	file, err := os.Create(filename)
	if err != nil {
		return fmt.Errorf("Error creating CSV file: %w", err) // 파일 생성 오류 시 에러 반환
	}
	defer file.Close()

	writer := csv.NewWriter(file)
	defer writer.Flush()

	for _, value := range data {
		if err := writer.Write([]string{value[0]}); err != nil {
			return fmt.Errorf("Error writing record to CSV file: %w", err) // 레코드 작성 오류 시 에러 반환
		}
	}

	logger.Info("Successfully saved data to CSV file") // 데이터 저장 성공 로그
	return nil
}

func fileLogger(filename string) *zap.Logger {
	config := zap.NewProductionEncoderConfig()
	config.EncodeLevel = zapcore.CapitalColorLevelEncoder
	config.EncodeTime = zapcore.ISO8601TimeEncoder
	fileEncoder := zapcore.NewJSONEncoder(config)
	consoleEncoder := zapcore.NewConsoleEncoder(config)
	logFile, _ := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	writer := zapcore.AddSync(logFile)
	defaultLogLevel := zapcore.DebugLevel
	core := zapcore.NewTee(
		zapcore.NewCore(fileEncoder, writer, defaultLogLevel),
		zapcore.NewCore(consoleEncoder, zapcore.AddSync(os.Stdout), defaultLogLevel),
	)

	logger := zap.New(core, zap.AddCaller(), zap.AddStacktrace(zapcore.ErrorLevel))

	return logger
}

func main() {
	filename := "logs.log"
	logger := fileLogger(filename)

	defer logger.Sync()

	var startIndex, endIndex int
	var err error

	// 터미널에서 시작 인덱스와 끝 인덱스를 입력받음
	fmt.Print("startIndex(0~184): ") // 0 -> 2
	_, err = fmt.Scan(&startIndex)
	if err != nil {
		logger.Fatal("Error reading start index", zap.Error(err)) // 시작 인덱스 읽기 오류 시 프로그램 종료
	}

	fmt.Print("endIndex(1~185): ")
	_, err = fmt.Scan(&endIndex)
	if err != nil {
		logger.Fatal("Error reading end index", zap.Error(err)) // 끝 인덱스 읽기 오류 시 프로그램 종료
	}

	// CSV 파일에서 질문 읽기
	questions, err := readCSV(INPUT_FILE, logger)
	if err != nil {
		logger.Fatal("Error reading CSV file", zap.Error(err)) // CSV 파일 읽기 오류 시 프로그램 종료
	}

	// 끝 인덱스가 0이거나 질문 수보다 큰 경우 질문 수로 설정
	if endIndex == 0 || endIndex > len(questions) {
		endIndex = len(questions)
	}

	var wg sync.WaitGroup
	results := make(chan [2]string, endIndex-startIndex)

	// 시작 인덱스부터 끝 인덱스까지 API 요청 생성
	for i := startIndex; i < endIndex; i++ {
		wg.Add(1)                                                // WaitGroup 카운터 증가
		go makeAPIRequest(questions[i], &wg, results, i, logger) // 고루틴으로 API 요청 실행
	}

	wg.Wait()      // 모든 고루틴이 완료될 때까지 대기
	close(results) // 채널 닫기

	var responseData [][2]string
	for response := range results {
		responseData = append(responseData, response) // 응답 데이터 수집
	}

	// 응답 데이터를 index 기준으로 오름차순 정렬
	sort.Slice(responseData, func(i, j int) bool {
		indexI, _ := strconv.Atoi(responseData[i][1])
		indexJ, _ := strconv.Atoi(responseData[j][1])
		return indexI < indexJ
	})

	// 정렬된 데이터의 텍스트 내용만 CSV 파일에 저장
	var sortedTextData [][2]string
	for _, value := range responseData {
		sortedTextData = append(sortedTextData, [2]string{value[0]})
	}

	// 응답 데이터를 CSV 파일에 저장
	if err := saveToCSV(OUTPUT_CSV_FILE, sortedTextData, logger); err != nil {
		logger.Fatal("Error saving to CSV", zap.Error(err)) // CSV 저장 오류 시 프로그램 종료
	}

	logger.Info("Processing complete", zap.String("outputFile", OUTPUT_CSV_FILE)) // 처리 완료 로그
	fmt.Printf("Processing complete. Results saved to %s\n", OUTPUT_CSV_FILE)     // 처리 완료 메시지 출력
}

 

읽어주셔서 감사합니다 🙇‍♂️

728x90

Golang으로 백엔드 개발 도전기

Go 언어

 

1. Intro

간단한 백엔드를 만들일이 있어서 고민을 하다가, 유튜브 알고리즘에 뜬 아래 두 동영상을 보고 Golang Backend를 공부해야겠다고 다짐했습니다. 약 4년전, 공군 개발병 복무 시절, Golang + go gin 으로 간단한 REST API를 사지방에서 만들어보고 초당 몇번의 요청이 가능한지 테스트해보고 빨라서 놀랐던 기억이 있었는데, 그 시절이 떠오르네요.
[Golang 도입, 그리고 4년 간의 기록 / Golang과 함께 서버 레이턴시를 500배 개선한 후기] 
https://www.youtube.com/watch?v=75X_eBW0mog

https://www.youtube.com/watch?v=NVqVS64qClk

2. 왜 Golang인가?

간단한 백엔드 개발을 위한 언어를 선택하는 과정에서 가장 중요하게 생각한 것은 성능이 빠른지와 단순한지였습니다. Golang은 다음과 같은 매력적인 장점들이 있습니다.

빠른 컴파일 속도
Golang의 빠른 컴파일 속도는 개발 생산성을 크게 향상시킵니다. 공군 시절 Spring 프로젝트를 수정하고 컴파일 하는데 1분 가까이 걸려서 멍때렸던 기억이 있었는데, 아직 경험해보지 못했지만 go는 프로젝트가 커져도 빠른 컴파일이 된다고 합니다.
뛰어난 동시성 처리
Go routine과 채널을 통한 동시성 제어는 Golang의 큰 장점 중 하나입니다. 요즘 Sass(Software as a service)가 많아지면서 Third party app(GPT etc..)과 API 연동할 일이 많은데 이때 강점을 가집니다.
언어의 단순성
간결하고 읽기 쉬운 문법으로 빠르게 학습할 수 있습니다. 사실 다른 빠른 언어들도 있지만, 개인적으로 go 언어가 이해가 잘되었습니다.
성능 최적화
메모리와 CPU 사용을 쉽게 최적화할 수 있다고 합니다. 고성능 애플리케이션 개발에 많이 사용된다고 합니다.

물론, Golang에도 몇 가지 단점이 있습니다..

에러 처리의 복잡성
모든 에러를 명시적으로 처리해야 해서 코드가 길어질 수 있습니다.
GC 관련 이슈
대규모 시스템에서는 가비지 컬렉션으로 인한 성능 저하가 발생할 수 있습니다.
제네릭 지원 부족
제네릭 기능의 부재로 인해 코드 재사용성이 떨어질 수 있습니다.
특정 패턴 강제
에러 처리 등에서 특정 패턴을 따라야 하는 경우가 많습니다.

3. 선택한 기술 스택

Golang 커뮤니티를 탐색하면서 많은 개발자들이 추천하거나 github star가 많은 라이브러리를 찾았습니다. 제가 선택한 기술 스택은 다음과 같습니다:

Go Gin
경량화되고 빠른 웹 프레임워크로, RESTful API 개발에 적합합니다.
SQLC
SQL 쿼리를 Go 코드로 변환해주는 도구로, 타입 안정성과 성능을 모두 잡을 수 있습니다.
PostgreSQL with RDS
요즘 많이 쓰는 관계형 데이터베이스입니다.
Docker
개발 환경의 일관성을 유지할 수 있습니다.
Github Action CI/CD
easy..!

4. 특이했던 점

NestJS에서 사용했었던 schema.prisma처럼 migration 폴더에 데이터베이스 정보(PK, FK, Constraint, relation, type 등)를 기술하는 것. @Controller, @Service 같은 어노테이션이 없는것. pointer star (*), & 등 C 같은 느낌. 특이한 for문 등. 내일은 AWS RDS 데이터베이스 만들고, 연결해봐야겠다. 그리고 메모장같은 간단한 CRUD를 만들어보고 GPT나 Claude API 연동 시도해봐야겠다..! 

 
읽어주셔서 감사합니다 😊
 
 

728x90

일기: 스케쥴이 가장 많았던 하루 🥵

안녕하세요! 다들 오늘 하루 어떻게 보내셨나요? 제 오늘은 정말 다채로운 하루였습니다. 평소에는 다들 재택근무를 해서 회사에 가지 않다가, 오늘은 전사회의가 있는 날이라 모두가 출근했어요. 그래서 다양한 일정들과 활동이 겹치면서 정신없이 지나갔네요. 오래만에 이렇게 일정이 많은 날도 있구나 싶어 기록해두고 싶어졌습니다.

구글에서 가져온 이미지


1. 점심 식사

요즘 장마철이라 계속 비가 와서 습하고, 날씨가 좋지 않았는데, 다행히 오늘은 맑았어요. 오랜만에 직원분들이 한자리에 모여 점심을 먹었습니다. 인턴 분이 추천한 지하에 있는 한식집에 갔는데, 반찬도 푸짐하고 은근히 맛있더라고요. 오랜만에 모여서 그런지 다들 못다한 이야기를 했습니다. (컨퍼런스 다녀온 이야기, 근황, 하는 일은 어떤지.. 등)

김치찌개 + 제육볶음 👍

2. 즉흥적인 탁구 경기

점심 식사 후에 카페를 갈까 하다가 갑자기 회사 옥상에 있는 탁구대가 생각나서 셋이서 탁구를 치기로 했어요. 놀랍게도 다들 실력이 대단하더라고요. 3명이 번갈아가며 11점 내기로 2판씩 했는데, 커트, 드라이브를 너무 잘하셨어요. 제가 생각보다 실력이 부족하다는 걸 깨달았습니다. 드라이브가 자꾸 공이 떠서 넘어가서 실점을 많이했습니다. 연습을 더 해야겠어요. 🥲

탁구대

3. 산업안전보건교육

오후 1시 30분부터는 외부 기업에서 진행하는 산업안전보건교육을 들었습니다. (국가에서 주관?) 건강박수 같은 레크레이션이나 재테크 지식(절세 등) 공유 같은 부분은 재미있었는데, 마지막에 연금 보험 상품 가입을 권유하셨는데 개인적으로는 보험보다 미국 주식 투자가 더 매력적이게 보여서 가입은 사양했습니다.

4. 전사회의

3시 30분부터는 전사회의가 있었습니다. 회사의 방향성과 계획, 그리고 우리가 만들어가야 할 조직문화에 대해 이야기를 나눴어요. 건의사항도 듣고 앞으로의 계획도 공유하는 시간이었습니다.

5. 저녁 식사

5시 30분에는 우리 회사 합류를 고민 중인 외국인 연구원분과 직원분들과 함께 인도 음식점에서 저녁을 먹었어요. 식당에서는 카잔 케밥을 처음 먹어봤는데 고기가 정말 맛있더라고요. 박사님의 연구실이 제가 학부 때 수업을 들었던 과목의 교수님과 같다는 걸 알고 놀랐습니다. 그리고 대학원 생활에 대한 이야기를 영어로 해주시는데, 발음이 생각보다 좋으셔서 꽤 알아들을 수 있었습니다. 다만 영어로 대화하는 게 익숙하지 않아 듣기만 하고, 영어로 많은 말을 하지 못한 게 아쉬웠습니다. 영어 말하기를 더 공부해야겠어요. (OPIC?)

"nice to meet you, i'm cheolheeLee"
"i have a question!!"
"how long does it take to publish the paper?"
"is it related with XXX?, there are lots of fund"
"oh yes, yes" (제일 많이 한 듯..)
"my second major is mobility software and AI too."
"See you soon"
"Bye, Bye"
"Good luck"
...

사실 머리에 떠오르는 구체적인 질문은 더 많았지만, 질문할 타이밍이 안맞아서 입으로 뱉지 못해서 아쉬웠습니다. 


6. NestJS 백엔드 오프라인 스터디

마지막으로 저녁 7시 30분부터는 NestJS 백엔드 오프라인 스터디에 참석했어요. 다양한 배경을 가진 분들과 IT 관련 이야기를 나누며 즐거운 시간을 보냈습니다. 특히 교통사고를 당하셔서 생사에 기로에 놓이셨다가, 회복하고 나신 후에 코딩을 배우시는 분의 열정에 감동받았어요. 상대적으로 나이 때문에 스터디에 참가하시기 주저하셨지만, 실리콘밸리에서는 50대도 공부하신대요 라고 위로해드렸습니다 👏. 또 "병원 생활 4년 사이에 IE가 사라졌어요"(인터넷 익스플로러)는 말을 "아이가 사라졌다" (아이 실종..!)는 말로 잘못 들어서 웃겼었던!

 

7.  마무리

이렇게 점심회식, 탁구 대결, 기업교육, 전사회의, 저녁회식, 스터디까지... 정말 많은 일정을 소화한 하루였습니다. 피곤하긴 하지만, 이렇게 다양한 경험을 할 수 있어 기억에 남는 하루였네요. 앞으로도 이런 날들이 가끔은 있었으면 좋겠습니다. 읽어주셔서 감사합니다 🙇‍♂️

728x90

2024년 인기있는 60가지 AI 도구들: AI 마법사가 되어보자!

 

 

참고
https://x.com/eyishazyer/status/1807732216484536399

안녕하세요, 여러분! 👋 오늘은 제가 트위터를 구경하다 우연히 발견한 신기하고 유용한 정보를 여러분과 공유하려고 해요. 2024년에 인기있는 60가지 AI 도구들이에요! 🚀💼 저도 지금 하나씩 사용해보고 있는 중인데, 몰랐던 도구들이 진짜 많기도하고 신기해서 여러분께 꼭 알려드리고 싶었어요! (제가 사용해본 것 형광펜 표시해볼게요)

1. 아이디어 발상 도구들 💡

Claude, ChatGPT, Gemini, Bing Chat, Perplexity 같은 AI 챗봇들이 아이디어 뱅크 역할을 해줘요! 상상력의 한계를 넓혀보세요!

2. 웹사이트 제작 도구들 🌐

@wegic_ai, Unicorn, 10Web, Framer, Dora로 멋진 웹사이트를 뚝딱 만들 수 있어요. 코딩 실력이 없어도 괜찮아요! 

아래 페이지가 Framer로 무료로 템플릿이 공개된 것인데 퀄리티가 좋네요! (다만 코드 수정을 하는 방법은 저도 더 찾아봐야 겠어요!!)
https://atomic-template.framer.website/

 

Atomic – Creative Business Landind Page Template for Framer

Atomic is the Framer template made for all type of businesses seeking to showcase their prowess in the most visually stunning way. Atomic provides a seamless canvas for your services, ensuring that your work always speaks volumes and impress your clients.

atomic-template.framer.website

 

3. 글쓰기 도구들 ✍️

Rytr, Jasper, Longshot, Textblaze, Copymate가 여러분의 글쓰기를 도와줄 거예요. 작가의 고민을 덜어주는 마법 같은 도구들이죠!

4. 회의 도구들 🗣️

Tldv, Krisp, Otter, Fathom, Airgram으로 회의를 더 스마트하게! 노트 정리부터 음성 인식까지 척척 해결해요.

5. 챗봇 도구들 🤖

SiteGPT, Chatbase, Chatsimple, CustomGPT, Mutual.info로 여러분만의 AI 비서를 만들어보세요!

6. 자동화 도구들 ⚙️

Make (Form, Slack 등 다양한 워크플로우 생성), Levity, Zapier, Xembly, Bardeen으로 반복적인 작업은 AI에게 맡기고 창의적인 일에 집중하세요!

7. UI/UX 도구들 🎨

Figma(곧 AI 기능 도입), Uizard, UiMagic, InstantAI, Galileo AI, Photoshop으로 멋진 디자인을 뚝딱!

8. 이미지 생성 도구들 🖼️

Leap AI, Astira AI, Midjourney, Bing create, Stable Diffusion으로 상상 속 이미지를 현실로!

9. 비디오 제작 도구들 🎥

Eightify, InVideo, HeyGen, Runway, Morphstudio.xyz로 고품질 영상을 만들어보세요!

10. 디자인 도구들 🎭

Canva, Flair AI, Clipdrop, Booth AI, Autodraw로 누구나 디자이너가 될 수 있어요!

11. 마케팅 도구들 📣

Pencil, Ai-Ads, AdCopy, Simplified, AdCreative로 효과적인 캠페인을 만들어보세요!

12. 트위터 도구들 🐦

Typefully, Tweetlify, Postwise, Tribescaler, TweetHunter로 트위터를 활성화해보세요!

요즘 이 도구들을 하나씩 사용해보면서 매일 새로운 인사이트를 발견하고 있어요. ("AI로 이런 기능도 만들 수 있구나!, 어떻게 개발했지..?") 여러분도 한번 도전해보세요! 처음엔 어색할 수 있지만, 조금씩 익숙해지다 보면 어느새 AI 마법사가 되어 있을 거예요. 😉🧙‍♂️

읽어주셔서 감사합니다 🙇‍♂️

728x90

[Cursor IDE + Claude 3.5 Sonnet]: VS Code IDE + GitHub Copilot에서 갈아탄 후기

Cursor

 

1. Intro

안녕하세요, 여러분! 오늘은 제가 Cursor IDE + Claude 3.5 Sonnet으로 개발 도구를 변경한 후기에 대해 이야기해볼까 해요. 🚀

저는 그동안 Visual Studio Code와 GitHub Copilot을 사용해왔어요. 꽤 만족하면서 쓰고 있었죠. 그런데 오늘 일하다가 개발자로 일하는 친척에게서 연락이 왔어요! 😮

 

 

2. Cursor IDE 사용해보기

https://www.cursor.com/

https://claude.ai/new

코딩 입문 때부터 계속 쓰던 VS Code에서 벗어나기가 두려웠지만, 한 번 사용해보자는 실험 정신도 있었고 열정적인 추천에 솔깃해서 바로 구독하고 설치해봤는요. 일하다 말고 툴을 바꾸는 게 좀 도전적이라고 생각했지만.. 근데 VS code의 기능과 UI/UX가 매우 비슷해서 이질감을 못느꼈어요. 코드 생성형 AI 모델 선택도 가능하더라고요. Claude-3.5도 지원이 되어서 최근에 핫한 이 모델을 선택해서 쓰고 있어요.

 

3. 좋았던 점

명령어 생성 및 터미널 디버깅

기존에는 에러가 발생하면 터미널 내용을 복사해서 구글에 검색하곤 했는데, [Option + D] 단축키를 통해 터미널을 디버깅하고 설명해주는 게 편했어요.

 

터미널에 커서를 옮기고 [Command + K]를 누르면 아래 채팅이 뜨고 원하는 명령어를 입력하고 Enter를 누르면 터미널에 명령어가 작성됨.

 

변경하고 싶은 코드만 수정 가능, IDE에서 직접 변경 가능

기존에는 변경하고 싶은 코드를 gpt 복붙하곤 했는데 IDE에서 직접 하니까 편했어요. 그리고 변경된 코드를 보고 번의 클릭으로 반영할 것인지, 취소할 것인지 선택할 수도 있어서 좋았어요. 그리고 최근 출시된 Claude-3.5라서 오류가 생기는 느낌도 있었어요. 기능 덕분에 생산성이 폭발하는 느낌이에요 💥..!

 

변경하고 싶은 코드 부분을 드래고하고 [Command + L]를 누르면, 채팅 사이드바가 뜬다(코드가 입력됨).

 

코드가 결과로 나오고, 상단의 [Apply] 클릭하면 수정될 부분 보여주고, [Accept]/[Reject]를 선택할 수 있다.

 

4. 마무리

VS Code와 GitHub Copilot도 아직도 사람들이 많이 사용하는 유용한 툴이지만, Cursor IDE와 Claude 3.5 Sonnet 조합은 꽤 신선한 같아요. 앞으로 계속 사용할 것 같네요. 여러분도 한번 시도해보는 건 어떨까요?(+구독료는 덤) 새로운 경험에 푹 빠질지도 몰라요! 😉 

 

Claude...! 너란 녀석

 

여러분은 어떤 도구를 쓰고 있나요? 각 도구의 좋았던 경험을 공유해주시면 정말 좋을 것 같아요! 자, 이제 저는 Cursor와 함께 코딩 여정을 시작하러 가볼게요. 다들 즐거운 코딩하세요! 읽어주셔서 감사합니다! 👋 

728x90

 

아래 명령어에 파일명(./docker.sh)을 넣고 실행하면, 원격 레포지토리에 push 하면 해당 파일에 대한 기록이 모두 사라진다..! 실수로 중요한 정보를 원격 레포지토리에 올린 경우 유용하게 사용할 수 있을 것 같다. 그리고 .gitignore을 통해 원격 레포지토리에 올라가지 않도록 잘관리하자!

$git filter-branch -f --index-filter "git rm --cached --ignore-unmatch [파일명]" --prune-empty -- --all
git push --force --all  // push 해주면 원격 저장소에 잘 반영됩니다.




https://cobi-98.tistory.com/48

 

 

[Git] GitHub 저장소의 특정 파일, 디렉토리 커밋기록을 모두 제거하기

GitHub GitHub는 로컬저장소를 이용한 빠른 퍼포먼스와 브랜치를 통한 효율적인 협업을 이룰 수 있으며, 등록 사용자와 비등록 사용자 모두에게 사이트의 공개 저장소를 찾아볼 수 있는 권한을 제

cobi-98.tistory.com

 

 

 

'개발 > Github' 카테고리의 다른 글

SVN에서 Git으로 전환하기  (1) 2021.01.25
9.2 Git과 여타 버전 관리 시스템 - Git으로 옮기기  (0) 2021.01.25
728x90

💻대전 개발자, 디자이너, 기획자, 학생을 대상으로 모임을 운영합니다. 
(2024.05.22 창설)

✅모임을 통해 얻어갈 수 있는 것
소프트웨어 개발 능력 향상 
소프트웨어 사업의 비즈니스 모델 이해 
코딩으로 문제를 해결하는 사고 탑재 
디자인적 감각 향상 ( UI/UX ) 
글로벌 AI 트렌드 지식 습득

🤗안녕하세요. '나는 솔로프리너'입니다.

🙋‍♂️솔로프리너(Solopreneur)란 단어 들어보셨나요? Solo + Enterpreneur를 합친 말로 1인 기업가를 의미합니다. 

🚀'나는 솔로프리너'는 솔로프리너 개념을 국내에 널리 알리고, 회원분들이 스스로를 주체적인 솔로프리너로 성장시키는 것을 목표로 합니다. 이는 N잡러 시대, 핵개인의 시대가 도래함에 따라 중요해지는 개인의 가치 제공 능력에 초점을 맞춥니다.

💬모임은 IT 최신 소식 제공, 체계적인 IT 교육, 질문 환경 조성, 토론 문화 조성, 글쓰기 문화를 지향합니다.

🙌모든 회원분들이 솔로프리너로써 우뚝 설 수 있게 만드는 것이 목표입니다. 자율적인 스터디도 가능합니다!

✍️IT기술 블로그 운영
https://cheolheelee.tistory.com/

🙋‍♂️모임장 소개
컴공 전공, 공군 개발병, 개발 사업 등

 

728x90


GPT-4o :: 강인공지능시대가 가까워지고 있다


OpenAI는 실시간 대화, Q&A, 텍스트 생성 등을 지원하는 다중 모드 대형 언어 모델인 GPT-4o를 공개했습니다. OpenAI는 생성형 AI 시대를 주도하는 기업입니다. OpenAI의 성공과 인기를 이끄는 핵심은 GPT-3와 GPT-4를 포함한 대형 언어 모델(LLM)인 GPT 시리즈와 회사의 ChatGPT 대화형 AI 서비스입니다.

OpenAI는 2024년 5월 13일 봄 업데이트 행사에서 새로운 대표 멀티모달 언어 모델인 GPT-4 Omni(GPT-4o)를 발표했습니다. 행사에서는 모델의 직관적인 음성 응답 및 출력 기능을 보여주는 여러 비디오도 공개되었습니다.

GPT-4o란 무엇인가요?

GPT-4o는 OpenAI LLM 기술 포트폴리오의 대표 모델입니다. 여기서 O는 Omni를 의미하며, 이는 단순한 마케팅 용어가 아니라 텍스트, 비전, 오디오의 여러 매체를 아우르는 모델의 특성을 나타냅니다.

GPT-4o 모델은 OpenAI가 2023년 3월 처음 출시한 GPT-4 LLM의 새로운 진화를 보여줍니다. 이는 GPT-4의 첫 번째 업데이트도 아닌데, 2023년 11월에 GPT-4 Turbo가 처음 등장하며 모델이 한 차례 향상된 바 있습니다. GPT는 Generative Pre-Trained Transformer의 약자입니다. 트랜스포머 모델은 생성형 AI의 기본 요소로, 새로운 출력을 이해하고 생성할 수 있는 신경망 아키텍처를 제공합니다.

GPT-4o의 기능은 무엇인가요?

GPT-4o가 출시될 당시, 이 모델은 기능성과 성능 면에서 모든 OpenAI 모델 중 가장 뛰어났습니다. GPT-4o가 할 수 있는 여러 가지 기능은 다음과 같습니다:

실시간 상호작용: GPT-4o 모델은 실시간으로 지연 없이 음성 대화를 나눌 수 있습니다.

지식 기반 Q&A: 이전의 모든 GPT-4 모델과 마찬가지로, GPT-4o는 방대한 지식 기반을 학습하여 질문에 답변할 수 있습니다.

텍스트 요약 및 생성: GPT-4o는 텍스트 요약 및 생성 등 일반적인 텍스트 LLM 작업을 수행할 수 있습니다.

멀티모달 추론 및 생성: GPT-4o는 텍스트, 음성, 비전을 하나의 모델로 통합하여 다양한 데이터 유형을 처리하고 응답할 수 있습니다. 모델은 오디오, 이미지, 텍스트를 동일한 속도로 이해할 수 있으며, 오디오, 이미지, 텍스트로 응답을 생성할 수 있습니다.

언어 및 음성 처리: GPT-4o는 50개 이상의 다양한 언어를 처리할 수 있는 고급 기능을 가지고 있습니다.

감정 분석: 이 모델은 텍스트, 오디오, 비디오의 다양한 모달리티에서 사용자 감정을 이해할 수 있습니다.

음성의 뉘앙스: GPT-4o는 감정적인 뉘앙스를 담은 음성을 생성할 수 있어, 민감하고 뉘앙스 있는 소통이 필요한 응용 프로그램에 효과적입니다.

오디오 콘텐츠 분석: 이 모델은 음성 언어를 생성하고 이해할 수 있으며, 음성 인식 시스템, 오디오 콘텐츠 분석 및 인터랙티브 스토리텔링에 적용될 수 있습니다.

실시간 번역: GPT-4o의 멀티모달 기능은 한 언어에서 다른 언어로 실시간 번역을 지원할 수 있습니다.

이미지 이해 및 비전: 모델은 이미지와 비디오를 분석할 수 있으며, 사용자가 업로드한 시각적 콘텐츠를 이해하고 설명하며 분석할 수 있습니다.

데이터 분석: 비전 및 추론 능력을 통해 데이터 차트에 포함된 데이터를 분석할 수 있습니다. GPT-4o는 분석이나 프롬프트에 따라 데이터 차트를 생성할 수도 있습니다.

파일 업로드: 지식 컷오프를 넘어, GPT-4o는 파일 업로드를 지원하여 사용자가 특정 데이터를 분석할 수 있습니다.

메모리 및 맥락 인식: GPT-4o는 이전 상호작용을 기억하고 긴 대화에서도 맥락을 유지할 수 있습니다.

큰 컨텍스트 창: 최대 128,000개의 토큰을 지원하는 컨텍스트 창을 통해, GPT-4o는 긴 대화나 문서에서도 일관성을 유지할 수 있어 상세한 분석에 적합합니다.

환각 감소 및 향상된 안전성: 이 모델은 부정확하거나 오해의 소지가 있는 정보를 최소화하도록 설계되었습니다. GPT-4o는 사용자가 안전하게 이용할 수 있도록 강화된 안전 프로토콜을 포함하고 있습니다.

 

 

728x90

iOS Flutter 프로젝트에서 flutter_downloader 1.11.4 통합하기

 

https://pub.dev/packages/flutter_downloader#_development-note_

 

커뮤니티 & 프롬프트 기반 이미지 생성 AI 앱을 AppStore에 배포하려고 했으나, 흰 화면이 뜨고 앱이 crash 나는 문제가 발생했습니다.

XCode의 에러 로그..

원인을 찾지 못해 방황하던 중에 은인을 만나서 원인을 파악하고 해결을 시도해보았습니다

flutter_downloader 플러그인, 특히 버전 1.11.4를 iOS Flutter 프로젝트에 통합하는 과정에서 ios 관련 설정이 누락되어 발생한 오류였습니다.

flutter_downloader란 무엇인가?

flutter_downloader 플러그인은 Flutter 애플리케이션에서 파일을 관리하고 백그라운드에서 다운로드하는 종합적인 기능을 가진 라이브러리입니다. 특히 큰 파일을 다룰 때 유용하여 사용자의 매끄러운 다운로드 경험을 도와줍니다.

iOS에서 flutter_downloader 1.11.4 설정하기

이 플러그인을 iOS 애플리케이션에 통합하기 위해서는 특정 설정이 필요합니다. 이는 iOS 프로젝트의 AppDelegate 파일을 변경하는 것을 포함합니다. 다음은 단계별 가이드입니다

1단계: 필요한 라이브러리 가져오기

먼저 AppDelegate 파일에 필요한 라이브러리가 가져와져 있는지 확인합니다. 이는 UIKit, Flutter, 그리고 flutter_downloader를 포함합니다. 코드는 다음과 같습니다:

import UIKit
import Flutter
import flutter_downloader

 

위 코드는 네이티브 iOS 기능과 Flutter 프레임워크의 기능, 특히 flutter_downloader 플러그인에 액세스하는 데 필요합니다.

2단계: AppDelegate 클래스 수정하기

다음 단계는 AppDelegate 클래스를 수정하여 flutter_downloader 플러그인을 등록하는 것입니다. 아래는 수정된 AppDelegate 클래스입니다.

private func registerPlugins(registry: FlutterPluginRegistry) {
    if (!registry.hasPlugin("FlutterDownloaderPlugin")) {
       FlutterDownloaderPlugin.register(with: registry.registrar(forPlugin: "FlutterDownloaderPlugin")!)
    }
}

 

이 코드는 애플리케이션이 시작될 때 flutter_downloader 플러그인이 등록되어 사용할 준비가 되었음을 보장합니다.

3단계: registerPlugins 함수 구현하기

마지막으로, registerPlugins 함수를 구현해야 합니다. 이 함수는 플러그인이 이미 등록되었는지 확인합니다. 그렇지 않은 경우 등록을 진행합니다.

private func registerPlugins(registry: FlutterPluginRegistry) {
    if (!registry.hasPlugin("FlutterDownloaderPlugin")) {
       FlutterDownloaderPlugin.register(with: registry.registrar(forPlugin: "FlutterDownloaderPlugin")!)
    }
}

이 함수는 동일한 플러그인이 여러 번 등록되는 것을 방지하여 예상치 못한 동작을 막는 역할을 합니다.

 

728x90

 

서론

  • 카카오 로그인 통합의 중요성 및 일반적인 문제 소개
  • 이 글에서 해결할 주요 에러: "invalid android_key_hash or ios_bundle_id or web_site_url"

Java 버전 확인 및 JDK 설치

  • 필요성: 카카오 로그인 통합을 위한 첫걸음으로, 올바른 Java 개발 환경 설정이 필수
  • Java 버전 확인 방법
    • 명령어: java --version
  • JDK 설치 방법
    • Homebrew를 이용한 OpenJDK 11 설치
      • 명령어: brew install openjdk@11
  • JDK 경로 설정
    • .zshrc 파일을 통한 경로 설정
      • 명령어: echo 'export PATH="/opt/homebrew/opt/openjdk@11/bin:$PATH"' >> ~/.zshrc
      • 설정 적용: source ~/.zshrc

Debug 키 해시 생성 및 카카오 개발자 사이트에 등록

  • Android Key Hash 생성
    • 필요성: 카카오 로그인을 위한 안드로이드 앱 인증 절차
    • Key Hash 생성 명령어:
      • keytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore -storepass android -keypass android | openssl sha1 -binary | openssl base64
  • 카카오 개발자 사이트에 키 해시 등록
    • 상세한 단계별 등록 방법 설명

결론

  • 이러한 단계를 통해 카카오 로그인 통합 시 발생할 수 있는 주요 문제 해결
  • 추가적인 문제 해결 팁 및 유용한 리소스 링크 제공

참고 자료

  • 카카오 개발자 문서, JDK 설치 가이드 등

+ Recent posts