라벨이 IT인 게시물 표시

로컬 LLM 구축하기: Ollama 설치부터 모델 실행까지

목차 시작 전 하드웨어 확인 Ollama 설치 모델 다운로드와 실행 문제 해결과 운영 팁 결론 이 글은 Linux와 macOS에서는 터미널 명령으로, Windows에서는 설치 프로그램으로 Ollama를 설치한 뒤 로컬 LLM을 처음 실행하는 과정을 안내합니다. 로컬 실행은 프라이버시와 오프라인 사용 측면의 장점이 있지만, 실제로 사용하는 모델의 메모리 요구량과 연결된 앱의 데이터 전송 여부도 함께 확인해야 합니다. 시작 전 하드웨어 확인 하드웨어 요구 사항은 모델 크기에 따라 달라집니다. 아래 RAM 수치는 시작 전에 확인할 권장 기준입니다. 모델 크기 권장 RAM 3B 8GB 이상 7~8B 16GB 이상 13B 32GB 이상 자신의 RAM이 기준에 못 미치면 작은 모델부터 선택하세요. Ollama 설치 Linux와 macOS에서는 터미널에서 설치 명령을 실행할 수 있습니다. Windows에서는 공식 설치 프로그램을 내려받아 실행한 뒤 터미널을 새로 열어야 합니다. 아래 명령은 Linux와 macOS용입니다. curl -fsSL https://ollama.com/install.sh | sh 설치가 끝나면 명령이 인식되는지 확인합니다. ollama --version ollama list 버전 정보가 표시되지 않으면 Linux/macOS에서는 command -v ollama 를 실행해 실행 파일 경로가 출력되는지 확인합니다. 아무것도 표시되지 않으면 터미널을 다시 열고 설치 명령을 다시 실행하세요. Windows에서는 설치 프로그램이 끝까지 완료되었는지 확인한 뒤 새 터미널에서 같은 명령을 실행합니다. 모델 다운로드와 실행 사용할 모델을 먼저 다운로드합니다. pull 은 모델 파일을 내려받아 로컬에 준비하는 단계이고, run 은 준비된 모델을 대화형으로 실행하는 단계입니다. 아래 예시는 3B급처럼 비교적 작은 모델부터 시작하는 경우에 사용할 수 있으며, 실제로 필요한 RAM은 앞의 표를 기준...

메모장 복구, %AppData% 뒤지지 마세요 — Windows 11은 다른 곳에 있습니다

이미지
먼저 어떤 메모장인지 확인하세요 "메모장 복구"로 검색해서 오시는 분들이 찾는 게 세 가지로 갈립니다. 복구 방법이 완전히 다릅니다. Windows 메모장 (notepad.exe) — 아래 내용 갤럭시 삼성 노트 / 아이폰 메모 — 클라우드 동기화 여부에 따라 다름 Notepad++ — 자체 백업 기능이 별도로 있음 이 글은 Windows 메모장 기준입니다. Windows 11이면 TabState를 먼저 보세요 탭 기능이 들어간 이후의 메모장은 명시적으로 탭을 닫지 않는 한 내용을 캐시에 계속 저장합니다. 강제 종료나 재부팅으로 날아간 경우라면 여기에 남아 있을 가능성이 높습니다. Win + R → AppData 입력 → 확인 Local → Packages → Microsoft.WindowsNotepad_8wekyb3d8bbwe → LocalState → TabState 이 폴더 안의 파일이 열려 있던 탭의 세션 데이터입니다. .bin 형식이라 더블클릭으로 열리지 않고, 텍스트 편집기로 열어도 내용이 온전히 보이지 않을 수 있습니다. 먼저 TabState 폴더를 통째로 다른 곳에 복사해 두십시오. 메모장을 다시 실행하면 이 파일들이 덮어써질 수 있습니다. 복사본을 확보한 뒤 메모장을 실행하면 세션이 자동 복원되는 경우가 대부분입니다. 왜 %AppData%\Roaming에는 없나 검색 결과 상위에 있는 글 대부분이 C:\Users\사용자명\AppData\Roaming 에서 .txt 를 찾으라고 안내합니다. 마이크로소프트 Q&A의 답변에도 그렇게 나옵니다. 그런데 Windows 11 메모장은 스토어 앱 형태라 Roaming이 아니라 Local\Packages 아래에 데이터를 둡니다. Roaming을 아무리 뒤져도 안 나오는 이유가 이겁니다. %AppData% 방법이 통하는 건 예전 데스크톱 버전 메모장이거나, 다른 프로그램이 임시로 만든 txt가 우연히 남아 있는 경...

AI 음성 생성(TTS)과 음성 인식(STT)의 원리 및 실생활 활용법

AI 음성 기술은 텍스트를 사람처럼 자연스러운 음성으로 바꾸고(TTS), 사람의 말을 정확하게 텍스트로 옮기는(STT) 기술을 통해 인간과 기계가 소통하는 방식을 근본적으로 변화시키고 있습니다. 이 기술은 일상의 편리함을 넘어 정보 접근성을 높이고 새로운 형태의 콘텐츠를 가능하게 합니다. 이 글에서는 AI 음성 기술의 핵심 원리를 살펴보고, 다양한 활용 사례를 통해 그 가치를 조명합니다. 목차 음성 생성(TTS)의 기본 원리 음성 인식(STT)의 기본 원리 음성 기술을 구동하는 주요 딥러닝 모델 AI 음성 기술의 핵심 활용 사례 결론 음성 생성(TTS)의 기본 원리 음성 생성(Text-to-Speech, TTS)은 글자를 소리로 바꾸는 기술입니다. 이 과정은 입력된 텍스트를 음운학적 단위로 분석해 음소(phoneme) 같은 음성 표현으로 변환하는 단계로 시작합니다. 이 음성 표현을 기반으로 AI 모델은 멜 스펙트로그램(mel spectrogram)과 같은 음향 특징을 생성하고, 마지막으로 보코더(vocoder)가 이 음향 특징을 실제 사람의 음성과 유사한 음향 파형으로 합성하여 소리로 출력합니다. 음성 인식(STT)의 기본 원리 음성 인식(Speech-to-Text, STT)은 사람의 음성을 글자로 변환하는 기술입니다. 이 기술은 마이크를 통해 들어온 음성 신호에서 특징을 추출한 뒤, AI 모델이 소리의 패턴과 언어적 문맥을 함께 분석하여 가장 가능성 높은 텍스트를 생성합니다. 주변 소음이나 말하는 사람의 특성에도 영향을 덜 받으며 잘 작동하는 것이 중요합니다. 음성 기술을 구동하는 주요 딥러닝 모델 AI 음성 기술의 핵심에는 방대한 데이터를 학습한 딥러닝 모델이 있습니다. 음성 생성(TTS) 분야에서는 Tacotron, FastSpeech, VITS 같은 모델과 트랜스포머(Transformer) 기반 아키텍처가 주로 활용됩니다. 최근에는 확산(Diffusion) 및 생성형 음성 모델도 연구되며, 이들은 텍스트의 문맥적 특징을 반영하여...

"부분 갱신 경로"라고 이름 붙이고 PUT으로 구현했다 — Blogger 라벨이 사라진 이유

Blogger 글 발행과 수정을 자동화하는 시스템을 운영하다가, 이미 발행된 글의 라벨이 수정 이후 사라지는 문제를 만났습니다. 라벨을 지우는 코드는 어디에도 없었고, 라벨을 만들어 전달하는 단계까지는 기록상 전부 정상이었습니다. 원인은 라벨 쪽이 아니라 글을 수정하는 HTTP 메서드에 있었습니다. 더 정확히는, PUT을 부분 수정으로 착각한 채 함수 이름과 주석에 "패치"라고 적어둔 것 이 원인이었습니다. 증상 게시글 목록을 훑다가 라벨이 하나도 없는 글 두 건을 발견했습니다. 글 ID 발행 시점 라벨 발견 시점 라벨 db9bd577 8개 0개 186c41e4 5개 0개 같은 시기에 발행된 다른 글들은 멀쩡했습니다. 특정 글에서만 사라진 셈이라, 처음에는 라벨을 생성하는 LLM 응답이 비어 있었거나 전달 과정에서 누락된 것으로 의심했습니다. 그런데 내부 기록 테이블 label_validation_logs 에는 두 글 모두 발행 시점에 8개, 5개를 Blogger로 전달한 기록이 남아 있었습니다. 만들지 않은 게 아니라, 만들어서 보냈는데 나중에 지워진 것이었습니다. 정상 구간부터 확정했다 라벨 생성 로직을 뜯어보기 전에, 어디까지가 정상인지를 먼저 잘랐습니다. 생성 단계 — posts.seo_tags 에 값이 저장돼 있음: 정상 전달 단계 — label_validation_logs 에 전달 기록 있음: 정상 최초 발행 — create_post() 는 POST로 새 글을 만들면서 라벨을 함께 실음: 정상 세 구간이 모두 정상이면, 라벨이 사라진 시점은 발행 이후에 그 글을 다시 건드린 요청 안에 있습니다. 발행된 글을 다시 건드리는 경로는 하나뿐이었습니다. 본문을 갱신하는 update_post_content() 입니다. 커밋을 거슬러 올라가니 답이 나왔다 client.put 이라는 문자열이 등장하고 사라진 커밋을 찾았습니다. git log --oneline -S ...

Synology vs QNAP — 홈 NAS 플랫폼, 무엇을 기준으로 골라야 할까

홈 NAS는 이제 단순한 파일 저장 장치를 넘어, 개인용 데이터 센터이자 자동화 서버의 핵심으로 자리 잡았습니다. 수많은 데이터를 안전하게 보관하고, 다양한 서비스를 24시간 운영하기 위해 어떤 NAS 플랫폼을 선택해야 할지 고민하는 분들이 많을 것입니다. 목차 NAS 선택의 주요 기준 Synology와 QNAP 비교표 Synology의 강점과 약점 QNAP의 강점과 약점 사용 상황별 선택 가이드 결론 NAS 선택의 주요 기준 NAS는 단순히 파일을 저장하는 것 이상으로, 미디어 서버, 백업 솔루션, 심지어는 웹 서버나 애플리케이션 플랫폼으로 활용될 수 있습니다. 특히 Docker와 같은 컨테이너 기술을 활용하면 그 활용 범위는 더욱 넓어집니다. 따라서 NAS를 선택할 때는 OS의 사용 편의성, 하드웨어 사양, 확장성, 그리고 애플리케이션 생태계와 Docker 지원 여부를 중요하게 고려해야 합니다. 저의 UGREEN DXP4800 Plus NAS는 단순 파일 서버를 넘어 kiwoom-bot , shorts-trend , n8n 같은 다양한 자동화 컨테이너를 Docker Compose로 운영하는 핵심 서버 역할을 합니다. PostgreSQL 데이터베이스까지 24시간 구동되죠. 이처럼 NAS를 단순히 저장 장치가 아닌 상시 구동되는 서버로 활용하려면, OS의 안정성과 Docker 지원 여부, 그리고 충분한 하드웨어 자원이 매우 중요합니다. Synology와 QNAP 비교표 Synology와 QNAP은 홈 NAS 시장의 대표적인 두 제조사입니다. 각 플랫폼의 특징을 비교표를 통해 살펴보겠습니다. 비교 항목 Synology (시놀로지) QNAP (큐냅) OS/UI DSM(DiskStation Manager), 직관적, 안정적 QTS(QNAP Turbo Station), 기능 풍부, 복잡 성능/하드웨어 보급형 모델 중심, 적당한 성능 고성능 모델 다양, 강력한 하드웨어 앱 생태계 사용자 친화적, 풍부한 ...

AI가 나를 속인 이야기: AI 언어 모델을 믿고 자동매매를 만들었다가 깨달은 것들

AI 언어 모델을 활용해 주식 자동매매 봇 개발을 시작하는 것은 매력적인 아이디어였습니다. 초기 개발 시간을 크게 단축할 수 있을 것이라는 기대감은 현실의 복잡한 기술적, 시장적 변수 앞에서 중요한 교훈을 안겨주었습니다. 이 글은 키움증권 Open API+를 활용한 자동매매 봇 개발 과정에서 AI 언어 모델을 사용하며 겪었던 경험을 공유합니다. 이 글은 투자 손실 위험에 대한 면책 문구를 포함하며, 실제 투자 결과보다는 기술 구현 과정에 초점을 맞춥니다. 또한, 키움 API는 Windows 32bit 환경이 필수라는 점을 전제로 합니다. 목차 AI가 단축시킨 개발 초반 현실과 부딪힌 AI 코드 AI 전략과 현실 시장의 괴리 결론 및 교훈 AI가 단축시킨 개발 초반 처음 자동매매 봇 개발을 구상했을 때, AI 언어 모델은 매력적인 출발점이었습니다. 기본적인 파이썬(Python) 코드 구조, 데이터베이스 연동(SQLite 또는 PostgreSQL), 스케줄링 로직 등 반복적인 초기 코드를 생성하는 데 많은 시간을 절약할 수 있었습니다. 특히, 기본적인 UI 구성(PyQt5)이나 웹 요청 처리 같은 일반적인 프로그래밍 패턴은 AI의 도움으로 빠르게 초안을 만들 수 있었습니다. AI 언어 모델은 키움 API 연동 코드 초안을 생성하는 데도 기여했습니다. 키움 API의 기본적인 함수 호출 방식이나 이벤트 처리 구조에 대한 파이썬 스켈레톤 코드를 제시했습니다. 덕분에 API 문서를 처음부터 전부 읽고 이해하는 시간을 절약하고, 실제 구현에 빠르게 착수할 수 있었습니다. 현실과 부딪힌 AI 코드 그러나 AI가 생성한 키움 API 연동 코드는 실제 작동 가능한 수준까지 많은 수정이 필요했습니다. 키움 API는 윈도우(Windows) 전용 32비트 환경과 파이썬의 PyQt5를 통한 COM 객체 연동이라는 특수성을 가집니다. AI 언어 모델은 이러한 낮은 수준의 운영체제 및 COM 인터페이스의 세부 동작을 정확히 이해하고 반영하지 못했습니다. 결국, 개...

Docker .env를 고쳤는데 왜 안 바뀔까

고쳤는데, 그대로다 NAS에서 돌아가는 블로그 자동화 서비스의 설정값을 하나 바꿔야 했습니다. .env 파일을 열어 값을 고치고, 컨테이너를 다시 올렸습니다. 그런데 아무것도 바뀌지 않았습니다. 파일을 다시 열어봐도 분명히 수정된 상태였습니다. 결론부터 말하면, 원인이 하나가 아니었습니다. 세 가지가 겹쳐 있었고, 그중 둘은 검색해도 잘 나오지 않는 종류였습니다. 이 글은 그 과정을 그대로 적은 기록입니다. 이 글은 Docker와 Docker Compose가 이미 설치되어 있고, 리눅스 계열 서버(또는 NAS)에서 컨테이너를 운영 중이라고 가정합니다. 원인 1: 같은 이름이면 environment가 우선입니다 Compose 파일에서 환경변수를 넣는 방법은 두 가지입니다. env_file 로 파일을 통째로 읽어오는 방법과, environment 에 직접 적는 방법입니다. services: backend: env_file: - .env environment: DATABASE_URL: postgresql://user:${POSTGRES_PASSWORD}@postgres:5432/db MONTHLY_BUDGET: 5 핵심은 이겁니다. 같은 이름의 변수가 env_file 과 environment 에 모두 있으면, environment 에 지정한 값이 우선 적용됩니다. 위 설정에서 DATABASE_URL 은 .env 에 적어도 반영되지 않습니다. Compose 파일에 같은 이름이 이미 있기 때문입니다. 제 경우가 정확히 이랬습니다. 양쪽에 중복으로 들어 있는 변수가 세 개 있었고, 하필 제가 고치려던 게 그중 하나였습니다. 파일은 분명히 바뀌었는데 컨테이너는 옛날 값을 쥐고 있던 이유입니다. 지금 내 프로젝트가 어떤 상태인지는 아래 명령으로 확인할 수 있습니다. Compose가 최종적으로 해석한 값을 보여줍니다. docker compose config Compose는 여러 곳에서 ...

API 호출 실패, 언제 재시도하고 언제 포기할까

실패했다고 다시 부르면, 더 나빠집니다 외부 API를 호출하는 코드를 짜다 보면 자연스럽게 이런 생각이 듭니다. "실패하면 몇 번 더 해보면 되겠지." 그래서 for 문으로 세 번 감싸고, 사이에 sleep 을 하나 넣습니다. 이게 위험합니다. 실패에는 다시 시도하면 성공하는 것과, 백 번을 해도 똑같은 것이 있습니다. 후자를 반복하면 응답 시간만 늘어나고, 최악의 경우 API 제공자로부터 호출량 제한에 걸립니다. 문제를 해결하려던 재시도가 새로운 문제를 만드는 셈입니다. 이 글은 블로그 자동 발행 서비스를 운영하면서 실제로 적용한 재시도 정책을 정리한 것입니다. Python과 httpx 를 예로 들지만, 원리는 어떤 언어에서든 같습니다. HTTP 상태코드의 의미를 어느 정도 알고 있다고 가정합니다. 상태코드별 처리표 결론부터 놓겠습니다. 제가 실제로 쓰고 있는 정책입니다. 상태코드 의미 처리 400 요청이 잘못됨 재시도하지 않음 401 인증 실패 토큰 갱신 후 1회만 재시도 403 권한 없음 재시도하지 않음 404 대상이 없음 재시도하지 않음 429 호출량 초과 Retry-After 확인 후 짧으면 대기, 길면 포기 5xx 서버 쪽 문제 지수 백오프 + 지터로 재시도 표를 관통하는 기준은 하나입니다. "내가 똑같이 다시 보내면, 결과가 달라질 여지가 있는가." 있으면 재시도하고, 없으면 즉시 포기합니다. 왜 이렇게 나눴나 400, 403, 404는 내 요청이 문제입니다. 필드 이름...

NAS Docker 컨테이너 데이터 자동 백업 및 복구 가이드

NAS에서 Docker 컨테이너를 운영하다 보면 언젠가 반드시 이 질문을 만나게 됩니다. "컨테이너를 지우거나 이미지를 새로 받으면, 안에 있던 데이터는 어떻게 되나?" 설정 파일, 데이터베이스, 업로드한 파일이 한순간에 사라지는 상황은 생각보다 흔합니다. 해결책은 단순합니다. 데이터가 실제로 저장된 호스트 경로를 찾아내고, 그 폴더를 주기적으로 압축해 다른 곳에 보관하는 것 입니다. 이 글에서는 그 경로를 확인하는 방법부터 셸 스크립트 작성, 크론탭 자동화, 그리고 실제 복구 절차까지 순서대로 다룹니다. 끝까지 따라 하면 매일 새벽 자동으로 백업이 쌓이고, 오래된 백업은 스스로 정리되며, 사고가 났을 때 몇 분 안에 되돌릴 수 있는 상태 가 됩니다. 목차 왜 Docker 데이터 백업이 필요한가 내 데이터가 어디 있는지 찾기 수동으로 한 번 백업해 보기 자동 백업 스크립트 작성 크론탭으로 자동화 데이터 복구 절차 가정 및 전제조건 이 글은 NAS에 Docker가 이미 설치되어 있고, SSH로 접속할 수 있으며, 컨테이너의 데이터가 바인드 마운트(Bind Mount) 로 호스트 경로에 연결되어 있다고 가정합니다. 아직 Docker를 설치하지 않았다면 NAS에 Docker 설치하기 를 먼저 참고하세요. 본문의 경로( /volume1/... )는 예시입니다. 실제 경로는 3번 단계에서 직접 확인합니다. 1. 왜 Docker 데이터 백업이 필요한가 컨테이너는 언제든 지우고 다시 만들 수 있도록 설계되어 있습니다. 이 성질이 장점이자 위험입니다. 이미지를 업데이트하거나 docker compose down 을 실행하는 것만으로도, 컨테이너 내부에만 있던 데이터는 함께 사라집니다. 반대로 데이터가 호스트 경로에 저장되어 있다면 컨테이너를 몇 번을 지우든 데이터는 남습니다. 이 원리를 다룬 사례로 Docker 재시작 후 PostgreSQL 데이터가 사라지는 이유와 영구 저장 방법 을 함께 읽어보면 이해가 빠릅니다. ...

httpx AsyncClient로 외부 API 비동기 호출하기

여러 외부 API를 호출하는 서버라면, 요청을 기다리는 동안 다른 일을 처리하는 비동기 방식이 유리합니다. 파이썬에서 비동기 HTTP 호출에 널리 쓰이는 httpx의 AsyncClient 사용법을, 실제 운영 중인 수집·발행 코드에서 쓰는 형태를 기준으로 정리합니다. 이 글의 가정 : async/await(파이썬 비동기 문법) 기본 개념을 알고 있고, Python 3.8 이상 환경을 가정합니다. 설치는 pip install httpx 입니다. AsyncClient 기본 사용 httpx는 동기 방식과 비동기 방식을 모두 지원합니다. 비동기에서는 AsyncClient 를 async with 블록으로 열고 await 로 요청을 보냅니다. import httpx async with httpx.AsyncClient(timeout=30) as client: resp = await client.post(url, headers=headers, json=payload) data = resp.json() async with 로 열면 블록이 끝날 때 클라이언트가 자동으로 정리됩니다. 타임아웃 설정 외부 API가 응답하지 않을 때 무한정 기다리지 않도록 timeout 을 지정하는 것이 중요합니다. 호출의 성격에 따라 값을 다르게 둘 수 있습니다. 예를 들어 인증 토큰 발급처럼 빨라야 하는 호출은 약 10초, 데이터 수집은 약 30초, 생성 시간이 긴 AI 언어 모델 호출은 약 40초처럼 맥락에 맞춰 정합니다. 응답 처리와 에러 스킵 여러 요청을 다룰 때는, 하나가 실패해도 전체가 멈추지 않도록 상태 코드를 확인하고 건너뛰는 방식이 실용적입니다. async with httpx.AsyncClient(timeout=30) as client: resp = await client.get(api_url, params={"q": keyword}) if resp.status_code != 200: prin...

Blogger API와 OAuth 2.0으로 블로그 글 자동 발행하기

블로그 글을 코드로 자동 발행하려면 Blogger API와 OAuth 2.0 인증이 필요합니다. 실제로 이 블로그를 운영하는 자동화 서버가 사용하는 흐름을 기준으로, 액세스 토큰 발급부터 글 생성과 공개 전환까지 정리합니다. 이 글의 가정 : Google 계정과 Blogger 블로그가 있고, Google Cloud에서 OAuth 클라이언트(client_id, client_secret)와 refresh token을 이미 발급받았다고 가정합니다. 발급 절차 자체는 Google 공식 인증 흐름을 따릅니다. 비밀값은 코드에 직접 쓰지 말고 환경변수로 관리합니다. OAuth 2.0 refresh token 흐름 refresh token(장기 보관용 재발급 토큰)은 한 번 발급받아 두고, API를 호출할 때마다 짧은 수명의 access token(실제 요청에 쓰는 접근 토큰)으로 교환해서 사용합니다. 즉 "refresh token → access token → API 호출" 순서입니다. 액세스 토큰 발급 토큰 교환은 Google 토큰 엔드포인트에 grant_type=refresh_token 으로 요청합니다. 아래는 비동기 HTTP 클라이언트 httpx를 사용한 예입니다. import os, httpx TOKEN_URL = "https://oauth2.googleapis.com/token" API_BASE = "https://www.googleapis.com/blogger/v3" async def get_access_token(): async with httpx.AsyncClient(timeout=10.0) as client: resp = await client.post(TOKEN_URL, data={ "client_id": os.environ["BLOGGER_CLIENT_ID"], "clie...

FastAPI에서 APScheduler로 주기 작업 구현하기

백그라운드에서 일정 간격으로 도는 작업(주기 수집, 정리 배치 등)이 필요할 때, 별도의 크론 서버 없이 FastAPI 앱 안에서 처리하는 방법을 정리합니다. 실제 운영 중인 유튜브 쇼츠 자동화 서버에서 트렌드 수집을 주기적으로 돌리는 코드를 기준으로 설명합니다. 이 글의 가정 : FastAPI 앱이 이미 동작하고 있고, Python 3.10 이상 환경을 가정합니다. 패키지는 pip install apscheduler 로 설치합니다. APScheduler란 APScheduler(Advanced Python Scheduler, 파이썬에서 예약 작업을 관리하는 라이브러리)는 "언제 무엇을 실행할지"를 코드로 등록해두면 그 시점에 함수를 대신 호출해 줍니다. FastAPI가 비동기(async) 기반이므로, 같은 이벤트 루프에서 도는 AsyncIOScheduler 를 사용합니다. 앱 생명주기에 스케줄러 붙이기 스케줄러는 앱이 켜질 때 시작하고, 꺼질 때 정리해야 합니다. FastAPI의 startup / shutdown 이벤트에 연결합니다. from apscheduler.schedulers.asyncio import AsyncIOScheduler scheduler = AsyncIOScheduler(timezone="Asia/Seoul") @app.on_event("startup") async def startup(): scheduler.start() @app.on_event("shutdown") async def shutdown(): scheduler.shutdown() timezone 을 명시하면 서버 로캘과 무관하게 의도한 시간대로 동작합니다. interval 작업 등록하기 가장 흔한 형태는 "일정 간격마다 반복(interval)"입니다. 수집 함수를 일정 주기로 등록합니다. @app.on_event("startup...

유튜브 쇼츠를 Python으로 자동 생성하는 방법 (실제 운영 중인 자동화 파이프라인 공개)

유튜브 쇼츠를 직접 촬영하지 않고 Python으로 자동 생성하는 시스템을 실제로 운영하고 있다. UGREEN DXP4800PLUS NAS에서 YouTube API로 트렌드 데이터를 수집하고, 성장률 분석 → 주제 선정 → 대본 생성 → 음성 합성 → 영상 합성까지 자동으로 처리한다. 현재 약 1,100개 영상 데이터를 수집 중이며, 30분마다 트렌드 분석을 수행한다. 이 글은 그 파이프라인 전체 구조와 실제로 막혔던 부분들을 정리한다. 전체 파이프라인 구조 YouTube API ↓ PostgreSQL (영상 데이터 저장) ↓ 성장률 분석 (6시간 / 24시간) ↓ AI 언어 모델 (대본 생성) ↓ Edge-TTS (한국어 음성 합성) ↓ FFmpeg (자막 + 배경 합성) ↓ drafts/ 폴더 저장 ↓ 수동 검토 ↓ YouTube 업로드 각 단계가 독립적인 Python 스크립트로 구성돼 있어서 문제가 생긴 단계만 수정하고 나머지는 그대로 쓸 수 있다. 1. PostgreSQL에서 트렌드 주제 선정 단순히 조회수 높은 영상이 아니라 6시간 성장률 기준으로 지금 뜨는 주제를 고른다. 성장률 계산 구조는 이전 글 PostgreSQL로 YouTube 조회수 성장률 계산하기 에서 다뤘다. import asyncpg async def get_trending_topics(): conn = await asyncpg.connect(DATABASE_URL) rows = await conn.fetch(""" SELECT v.title, v.content_cluster, s.growth_6h FROM videos v JOIN video_snapshots s ON v.video_id = s.video_id WHERE s.recorded_at = (SELECT MAX(recorded_at) FROM video_snapshots) ...

PostgreSQL로 YouTube 조회수 성장률 계산하기 (스냅샷 비교 방식 실전 예제)

이미지
YouTube 쇼츠 트렌드를 분석하면서 깨달은 게 있다. 조회수 자체는 별로 의미가 없다는 것이다. 조회수 100만짜리 영상도 6개월 전에 터진 거라면 지금 트렌드가 아니다. 반면 조회수 5,000인 영상이 어제 올라와서 24시간 만에 5,000이 찍혔다면 지금 뜨는 영상이다. 그래서 조회수 대신 성장률 을 기준으로 트렌드를 판단하는 구조를 만들었다. 현재 UGREEN DXP4800PLUS NAS에서 운영 중인 시스템에는 영상 약 1,100개, 스냅샷 약 74,000개가 쌓여 있다. 이 글은 그 구조를 만든 이유와 실제 PostgreSQL 쿼리를 정리한다. 왜 YouTube 조회수 스냅샷 방식이 필요한가 처음에는 단순하게 접근했다. 영상 정보를 수집할 때 현재 조회수를 같이 저장하면 된다고 생각했다. 문제는 바로 드러났다. 오늘 조회수: 12,000 어제 조회수: 알 수 없음 (덮어쓰여서 사라짐) 현재 값만 저장하면 변화량을 계산할 수 없다. 성장률은 이전 값과 현재 값의 차이 에서 나온다. 그래서 조회수를 덮어쓰는 대신 시간마다 찍어서 쌓는 스냅샷 방식을 선택했다. PostgreSQL 테이블 구조 설계 핵심 테이블은 두 개다. -- 영상 기본 정보 CREATE TABLE videos ( id SERIAL PRIMARY KEY, video_id VARCHAR UNIQUE NOT NULL, title VARCHAR, channel_name VARCHAR, tags TEXT, content_cluster VARCHAR, format_type VARCHAR, collected_at TIMESTAMP DEFAULT NOW() ); -- 시간별 조회수 스냅샷 CREATE TABLE video_snapshots ( id SERIAL PRIMARY KEY, video_id VARCHAR REFERENCES videos(video_id), ...

FastAPI와 PostgreSQL 연동하기 (Docker 환경 실전 예제)

UGREEN DXP4800PLUS NAS에서 FastAPI 기반 쇼츠 트렌드 수집 시스템을 운영하고 있다. 현재 DB에는 영상 700개 이상, 스냅샷 40,000개 이상이 쌓여 있고, 30분마다 자동으로 수집·저장이 돌아간다. 이 구조를 만들면서 FastAPI와 PostgreSQL 연동에서 막혔던 부분들을 정리한다. 단순한 예제 코드가 아니라 Docker Compose 환경에서 실제로 동작하는 구성을 기준으로 설명한다. 현재 운영 환경 NAS: UGREEN DXP4800PLUS (Debian 12 기반) FastAPI: Docker 컨테이너로 실행 PostgreSQL: 동일 Docker Compose 내 별도 컨테이너 ORM: SQLAlchemy 2.0 (비동기, asyncpg 드라이버) DB명: shorts / 주요 테이블: videos, snapshots 1. Docker Compose 구성 FastAPI와 PostgreSQL을 같은 Compose 파일 안에 두면 컨테이너 이름으로 서로 통신할 수 있다. services: db: image: postgres:15 environment: POSTGRES_DB: shorts POSTGRES_USER: user POSTGRES_PASSWORD: password volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U user -d shorts"] interval: 5s retries: 5 api: build: . ports: - "8105:8000" environment: DATABASE_URL: postgresql+asyncp...

UGREEN DXP4800PLUS NAS로 AI 자동화 시스템 구축하기 (Docker + PostgreSQL + YouTube API + n8n 완전 가이드)

이미지
 처음에는 단순히 유튜브 쇼츠 트렌드를 수집하려고 했다. 그런데 데이터를 저장할 DB가 필요했고, 자동화 워크플로가 필요했고, TTS가 필요했고, 컨테이너 관리가 필요했다. 하나를 해결하면 다음 문제가 보였다. 결국 하나의 AI 자동화 시스템으로 발전했다. 이 글은 UGREEN DXP4800PLUS NAS 위에서 실제로 운영 중인 AI 자동화 시스템의 전체 구조를 정리한 글이다. 각 컴포넌트를 왜 선택했는지, 어떻게 연결했는지를 중심으로 설명한다. 각 기술의 상세 내용은 개별 글 링크로 연결한다. 전체 아키텍처 YouTube API ↓ Collector (수집기) ↓ PostgreSQL (데이터 저장) ↓ FastAPI (대시보드 + API) ↓ Edge-TTS (음성 생성) ↓ Video Generator (영상 합성) ↓ drafts 폴더 저장 n8n (워크플로 자동화) ↓ Telegram (알림) 모든 서비스는 UGREEN DXP4800PLUS NAS에서 Docker 컨테이너로 운영된다. 왜 NAS를 선택했는가 클라우드 서버를 쓰면 월 고정 비용이 발생한다. 여러 서비스를 동시에 운영하면 비용이 빠르게 늘어난다. UGREEN DXP4800PLUS는 현재 20개 이상의 Docker 컨테이너를 동시에 운영하면서도 CPU와 메모리에 여유가 있다. 초기 구매 비용이 있지만 월 운영 비용은 전기세뿐이다. Docker 계층: 모든 서비스의 기반 Docker가 없었다면 PostgreSQL, FastAPI, n8n을 각각 별도로 설치하고 관리해야 했다. Docker Compose로 각 서비스를 컨테이너 단위로 격리하고 하나의 스택으로 관리한다. NAS 재부팅 후에도 restart: unless-stopped 설정으로 모든 서비스가 자동 복구된다. 처음 구축할 때 depends_on 만으로는 서비스 시작 순서가 보장되지 않는다는 것을 겪었다. healthchec...

n8n Telegram 연동 방법과 자동 알림 워크플로 구축하기

이미지
 매일 아침 정책자금 공고를 직접 찾아보는 게 번거로웠다. 기업마당(bizinfo.go.kr)에 매일 새로운 공고가 올라오는데 일일이 확인하기가 쉽지 않았다. n8n으로 자동화하면 되겠다는 생각에 워크플로를 만들었다. 결과적으로 매일 오전 9시에 새로운 정책자금 공고가 Telegram으로 자동 발송되는 시스템이 완성됐다. 이 글에서는 UGREEN DXP4800PLUS NAS에서 운영 중인 n8n으로 Telegram 자동 알림 워크플로를 구축하는 방법을 정리한다. n8n이란 n8n은 노코드/로우코드 워크플로 자동화 도구다. 200개 이상의 서비스와 연동할 수 있고 Docker로 셀프 호스팅이 가능하다. 현재 UGREEN DXP4800PLUS NAS에서 Docker로 운영 중이다. bash docker ps | grep n8n # n8n Up 6 days 0.0.0.0:5678->5678/tcp Telegram Bot 준비 n8n에서 Telegram으로 메시지를 보내려면 먼저 Telegram Bot을 만들어야 한다. 1. BotFather로 봇 생성 Telegram에서 @BotFather 검색 후 대화 시작: /newbot ↓ 봇 이름 입력 (예: MyAlertBot) ↓ 봇 username 입력 (예: my_alert_bot) ↓ API Token 발급 발급된 Token 형식: 1234567890:ABCdefGHIjklMNOpqrSTUvwxYZ 2. Chat ID 확인 봇에게 먼저 아무 메시지나 보내야 getUpdates 결과에 chat.id가 표시된다. 그 다음 아래 URL로 접속하면 된다: `https://api.telegram.org/bot{TOKEN}/getUpdates` n8n 설치 확인 n8n 웹 UI에 접속한다: http://NAS_IP:5678 처음 접속 시 계정 생성 후 로그인하면 워크플로 편집 화면이 나온다. 워크플로 구성 실제 운영 중인 워크플로 구조: Schedule Trig...