994 posts
의미 검색(Semantic Search)은 어떻게 동작할까: 임베딩과 벡터 유사도

의미 검색(Semantic Search)은 어떻게 동작할까: 임베딩과 벡터 유사도

전에 BM25 랭킹 알고리즘을 다루면서 마지막에 숙제를 하나 남겨뒀는데요. "환불"로 검색하면 정작 "결제 취소"가 적힌 안내 문서를 찾지 못한다는 키워드 검색의 약점이었습니다. 두 표현은 사람이 보기엔 같은 뜻이지만, 글자가 하나도 겹치지 않으니 단어 일치에 기대는 검색은 둘을 연결하지 못해요. 😅 이 빈틈을 메우는 게 바로 의미 검색(Semantic Search)입니다. 글자가 아니라 "의미"를 기준으로 문서를 찾는 방식이죠. 요즘 챗봇, 추천, RAG(검색 증강 생성)의 바탕에 거의 빠짐없이 깔려 있는 기술이기도 합니다. 이

BM25 알고리즘: 검색 엔진은 어떻게 관련도 순으로 정렬할까

BM25 알고리즘: 검색 엔진은 어떻게 관련도 순으로 정렬할까

검색창에 단어를 넣으면 결과가 주르륵 나오는데요. 그런데 이 결과들의 "순서"는 누가 정하는 걸까요? 단순히 단어가 들어간 문서를 다 모아서 보여주기만 한다면, 정작 내가 찾던 문서는 47번째쯤에 묻혀 있을 수도 있어요. 검색이 쓸모 있으려면 가장 관련 있는 문서가 맨 위로 올라와야 합니다. 이 "관련도 순 정렬"을 책임지는 게 바로 랭킹 함수입니다. 그리고 지난 20여 년간 텍스트 검색의 사실상 표준으로 자리 잡은 랭킹 함수가 바로 BM25예요. Elasticsearch, OpenSearch, Apache Lucene, SQLit

Rust 모듈 시스템 큰 그림: mod, use, pub이 맞물리는 방식

Rust 모듈 시스템 큰 그림: mod, use, pub이 맞물리는 방식

Rust를 배우다 보면 어느 순간 코드가 한 파일에 다 들어가지 않게 됩니다. 처음에는 main.rs 하나로 충분하지만, 타입이 늘고 함수가 많아지면 자연스럽게 파일을 나누고 싶어지죠. 그때 등장하는 키워드가 mod, use, pub입니다. 문제는 이 셋이 비슷한 위치에 자주 나타난다는 점입니다. 파일 위쪽에 mod config;가 있고, 바로 아래에 use crate::config::Config;가 있으며, 다른 파일에는 pub struct Config가 있죠. 처음 보면 "이게 다 import인가?", "왜 pub을 붙였는데 밖

Void로 프로덕션 앱 이전하기: 하루 만의 컷오버 실전 기록

Void로 프로덕션 앱 이전하기: 하루 만의 컷오버 실전 기록

지난번 Void는 Vite 네이티브 배포 플랫폼이라는 글에서 "이게 자바스크립트의 Rails가 될 수 있을까"를 다뤘는데요. 그때는 직접 써보지 않고 개념만 파헤친 소개글이었습니다. 이번엔 다릅니다. TanStack Start + Cloudflare Workers + D1으로 운영 중이던 실서비스를 하루 만에 Void로 컷오버하면서 직접 부딪힌 기록이에요. 결론부터 말씀드리면 코드 변경은 거의 없었고, 빌드는 3배 빨라졌고, 그리고 베타 제품답게 문서에 없는 지뢰를 다섯 개나 밟았습니다. 💥 이 글은 그 지뢰들을 어떻게 역공학으로

Rust에서 큐(Queue) 자료구조 사용하기: VecDeque

Rust에서 큐(Queue) 자료구조 사용하기: VecDeque

큐(queue)는 먼저 들어온 데이터가 먼저 나가는 선입선출(First In First Out, 이하 FIFO) 자료구조입니다. 줄을 서서 기다리는 모습을 떠올리면 됩니다. 먼저 줄을 선 사람이 먼저 처리되죠. 작업 대기열, 메시지 버퍼, 너비 우선 탐색(BFS) 같은 곳에서 빠지지 않고 등장합니다. 그런데 Rust를 막 시작한 분들은 큐가 필요할 때 일단 Vec에 손이 갑니다. Vec의 끝에 push로 넣고, 앞에서 remove(0)으로 빼면 되니까요. 동작은 하는데요, 문제가 하나 있습니다. Vec은 메모리에 데이터를 일렬로 붙

Void 환경변수: env.ts로 타입 안전하게, 시크릿까지

Void 환경변수: env.ts로 타입 안전하게, 시크릿까지

Void 인증을 붙이고 나면 곧바로 BETTER_AUTH_SECRET이라는 비밀 키를 마주하게 됩니다. 세션 토큰을 서명하는 값인데, 코드에 적을 수도 없고 깃에 올릴 수도 없죠. 비단 인증만의 문제도 아닙니다. Stripe 키, 웹훅 URL, 외부 API 토큰까지, 앱을 키우다 보면 "코드 밖에서 주입해야 하는 값"이 계속 늘어나거든요. 그런데 우리가 환경변수로 늘 겪던 불편이 있습니다. process.env.PORT는 항상 문자열이라 매번 숫자로 바꿔야 하고, 키 이름에 오타가 나도 런타임에야 터지고, 어떤 값이 필수인지는 RE

Cloudflare Access 신원 공급자(IdP) 연동 제대로 이해하기

Cloudflare Access 신원 공급자(IdP) 연동 제대로 이해하기

Cloudflare Access로 내부 대시보드를 막아두고 나면, 자연스럽게 다음 질문이 따라옵니다. "그래서 로그인은 대체 누가 처리하는 거지?" Access 설정 화면 어디에도 비밀번호를 저장하는 곳은 없고, 사용자 목록을 직접 만드는 메뉴도 보이지 않거든요. 그런데도 @mycompany.com 직원만 통과시키고, 인턴 그룹은 제외하고, MFA를 강제하는 정책이 멀쩡히 돌아갑니다 🤔 비밀은 Access가 인증을 직접 하지 않는다는 데 있습니다. 대신 외부 신원 공급자(Identity Provider, 이하 IdP)에게 "이 사

auth.md: AI 에이전트를 위한 회원가입 프로토콜

auth.md: AI 에이전트를 위한 회원가입 프로토콜

요즘 AI 에이전트가 단순히 질문에 답변만 하는 게 아니라, 실제로 사용자를 대신해서 외부 서비스를 호출하는 경우가 점점 늘고 있는데요. 예를 들어 코딩 에이전트한테 "Cloudflare에 새 워커를 배포해줘"라고 시키거나, 이메일 에이전트한테 "Resend로 뉴스레터 발송해줘"라고 부탁할 수 있습니다. 그런데 여기서 한 가지 문제가 생깁니다. 에이전트가 그 서비스에 가입되어 있어야 API를 호출할 수 있는데, 가입 자체가 사람을 전제로 만들어진 절차란 말이죠. 회원가입 폼, 이메일 인증, CAPTCHA, 대시보드 로그인 같은 단계

선배, 저 개발하지 말라고 해줘서 너무 고마워요

선배, 저 개발하지 말라고 해줘서 너무 고마워요

"선배, 저 개발하지 말라고 해줘서 너무 고마워요!" 얼마 전, 한국에서 함께 일하던 후배에게 연락이 왔습니다. 임원으로 승진했다는 반가운 소식이었습니다. 그 친구가 신입이었을 때 제가 사수를 맡았는데, 개발에는 소질이 없는 것 같다고 직언을 해준 적이 있습니다. 요즘이야 멘토링도 하고 그래서 조금 나아졌지만, 당시에 저는 돌려서 말하는 방법을 잘 몰랐습니다. 저의 묵직한 돌직구를 후배는 아직도 기억하고 있더라고요. 안 그래도 본인은 개발이 적성에 맞지 않는 것 같다고 느끼고 있었는데, 제 말이 계기가 되어 개발자로서의 커리어에 대

Satteri: Rust로 파싱하고 JavaScript로 확장하는 마크다운 처리기

Satteri: Rust로 파싱하고 JavaScript로 확장하는 마크다운 처리기

블로그나 문서 사이트를 운영하다 보면 빌드 시간이 슬금슬금 늘어나는 순간이 옵니다. 글이 수백 개를 넘어가면 마크다운 파싱과 변환에만 수십 초가 쓰이고, 거기에 remark/rehype 플러그인을 몇 개 더 끼우면 1분을 훌쩍 넘기는 경우도 흔하죠. 저도 이 블로그를 운영하면서 빌드 로그를 보다가 "마크다운 처리에 이만큼 시간을 쓴다고?"라는 생각을 한두 번 해본 게 아닌데요 😅 최근 Astro 6.4에서 markdown.processor API가 새로 등장하면서, unified 생태계가 아닌 다른 마크다운 처리기로 갈아탈 수 있

Void 인증: Better Auth로 로그인부터 라우트 보호까지

Void 인증: Better Auth로 로그인부터 라우트 보호까지

Void 데이터베이스에서 테이블을 만들고 Void 라우팅으로 API까지 열고 나면, 다음으로 부딪히는 벽은 늘 똑같습니다. "그래서 이 요청을 보낸 사람이 누구인지 어떻게 알지?"라는 질문이죠. 로그인 없는 앱은 결국 누구나 같은 화면을 보는 앱이니까요. 다행히 Void는 인증을 거의 공짜로 얹어줍니다. 따로 라이브러리를 고르고 세션 저장소를 붙이고 콜백을 짜는 그 지난한 과정 없이, 설정 한 줄과 헬퍼 몇 개로 로그인이 돌아가거든요. 이번 글에서는 인증을 켜고, 회원가입과 로그인을 붙이고, "로그인한 사람만 들어올 수 있는" 영역

tantivy로 시작하는 Rust 풀텍스트 검색: 스키마부터 쿼리까지

tantivy로 시작하는 Rust 풀텍스트 검색: 스키마부터 쿼리까지

데이터베이스에 LIKE '%검색어%'를 박아 쓰다 보면 한계가 금방 옵니다. 결과는 느리고, 오타에 약하고, 관련도 점수도 없습니다. 본격적으로 검색다운 검색을 붙이려면 Elasticsearch나 OpenSearch를 띄우는 게 정석이지만, 가벼운 사이드 프로젝트나 임베디드 환경에서 별도 서버까지 두기는 부담스러울 때가 있어요. tantivy는 Rust로 작성된 풀텍스트 검색 엔진 라이브러리입니다. Apache Lucene에서 영감을 받은 설계라서 한 번 배워두면 검색 엔진의 기본기를 그대로 익힐 수 있고, 별도의 데몬 없이 우리

MCP 2026-07-28 스펙: 세션을 버리고 무상태 프로토콜로

MCP 2026-07-28 스펙: 세션을 버리고 무상태 프로토콜로

MCP 서버를 직접 만들어보신 분이라면 한 번쯤 이런 고민을 해보셨을 것 같은데요. 서버를 여러 대로 늘려서 트래픽을 나눠 받으려는데, 클라이언트마다 세션이 묶여 있어서 같은 서버로만 요청을 보내야 하는 상황 말이죠. 로드밸런서에 스티키 세션(sticky session)을 걸거나 세션 정보를 따로 저장소에 빼두거나, 방법을 찾긴 하지만 어딘가 번거롭고 찜찜했습니다. 😅 지난 7월 28일에 최종 확정된 MCP 2026-07-28 스펙이 바로 이 지점을 정면으로 건드립니다. 프로토콜이 세상에 나온 이후 가장 큰 폭의 개정으로, 공동

Void 폼과 액션: useForm으로 끝까지 타입 안전하게

Void 폼과 액션: useForm으로 끝까지 타입 안전하게

Void 라우팅에서 데이터를 읽어 오는 로더는 자세히 봤는데요. 정작 데이터를 바꾸는 쪽은 "로더 대신 action을 내보낸다"는 한 줄로 넘어갔습니다. 사실 폼을 다루는 일은 읽기보다 손이 많이 가죠. 입력값을 검증하고, 에러를 화면에 표시하고, 제출 중에는 버튼을 막고, 성공하면 목록을 새로고침하고… 이번 글에서는 Void가 이 과정을 useForm 하나로 어떻게 묶어주는지 살펴보겠습니다. 액션으로 데이터 바꾸기 데이터를 읽을 때 로더를 썼다면, 바꿀 때는 액션을 씁니다. 페이지의 .server.ts에서 action을 내보내면

bat 사용법: cat을 대체하는 문법 강조 지원 뷰어

bat 사용법: cat을 대체하는 문법 강조 지원 뷰어

터미널에서 코드 파일을 빠르게 훑어보고 싶을 때 보통 cat file.py라고 입력하는데요. 출력은 잘 되지만 색도 없고 줄 번호도 없어서 조금만 길어져도 가독성이 뚝 떨어집니다. 그렇다고 매번 에디터를 켜기엔 번거롭고, less로 열면 페이징은 되지만 여전히 흑백이라 아쉽죠 😅 이런 답답함을 해결해 주는 도구가 바로 bat입니다. 이름에서 짐작하셨겠지만 cat을 본떠 만든 도구인데요. Rust로 작성됐고 ripgrep, fd처럼 "기존 유닉스 도구를 현대적으로 다시 만든" 계열에 속합니다. 문법 강조, Git 통합, 줄 번호,

Cloudflare Vectorize로 엣지에서 벡터 검색 구현하기

Cloudflare Vectorize로 엣지에서 벡터 검색 구현하기

요즘 챗봇이나 검색 기능을 만들다 보면 "의미 기반 검색"이 거의 필수가 됐어요. 사용자가 "환불 받고 싶어요"라고 입력해도 "결제 취소 절차"가 적힌 문서를 찾아줘야 하잖아요. BM25 같은 키워드 검색은 단어가 정확히 일치해야 하기 때문에 이런 경우에 약한데, 그 빈틈을 메우는 게 바로 의미 기반 검색입니다. 이런 의미 기반 검색을 구현하려면 텍스트를 벡터로 바꿔 저장하고 비슷한 벡터를 빠르게 찾아주는 벡터 데이터베이스가 필요합니다. Pinecone, Weaviate, Qdrant 같은 전문 서비스도 좋지만 Cloudflare

AWS MCP 서버: AI 에이전트로 AWS 다루기

AWS MCP 서버: AI 에이전트로 AWS 다루기

AWS 콘솔에 들어가서 메뉴를 뒤져가며 EC2 인스턴스를 띄우거나 S3 버킷 정책을 만져본 경험이 있으실 텐데요. CLI에 익숙하더라도 200개가 넘는 서비스의 API를 다 외울 수는 없어서 결국 문서를 뒤지면서 작업하게 되죠. 이런 작업을 AI에게 자연어로 시킬 수 있다면 어떨까요? AWS가 2026년 5월 6일 AWS MCP 서버를 정식 출시하면서 그게 가능해졌습니다. "us-west-2 리전에 t3.micro 인스턴스 하나 띄워줘" 같은 요청을 클로드 코드나 Cursor에서 그대로 처리할 수 있어요. 이번 글에서는 AWS MC

pgvector로 PostgreSQL에 벡터 검색 더하기

pgvector로 PostgreSQL에 벡터 검색 더하기

요즘 LLM을 활용한 서비스가 늘면서 "벡터 데이터베이스"라는 말을 자주 듣게 됩니다. 의미 검색이나 RAG를 구현하려면 임베딩 벡터를 저장하고 빠르게 찾아줄 곳이 필요하기 때문인데요. Pinecone, Weaviate, Qdrant처럼 벡터 검색에 특화된 제품도 있고요. 그런데 기존에 PostgreSQL을 쓰고 있는 팀이라면 굳이 새로운 데이터베이스를 하나 더 운영하는 건 부담스러울 수 있죠. 이때 좋은 선택지가 pgvector입니다. PostgreSQL 확장(extension) 형태로 동작하기 때문에 별도 서버를 띄울 필요 없이

presenterm으로 마크다운 발표 자료 만들기: 터미널에서 돌아가는 슬라이드

presenterm으로 마크다운 발표 자료 만들기: 터미널에서 돌아가는 슬라이드

개발자 모임이나 사내 세미나에서 발표 자료를 만들 때 늘 똑같은 고민이 들곤 합니다. Keynote나 파워포인트로 만들자니 코드 블록 하나 넣자고 폰트와 들여쓰기를 만지작거리는 게 영 번거롭고, 그렇다고 Google Slides는 코드 신택스 하이라이팅이 부실하죠. 마크다운으로 그냥 글 쓰듯 쓰면 안 될까 싶었던 적이 한두 번이 아닙니다. presenterm은 딱 그 가려운 곳을 긁어주는 도구입니다. 마크다운 파일 하나로 슬라이드를 만들고 터미널에서 바로 띄울 수 있어요. 코드 블록은 bat 기반의 신택스 하이라이팅이 자동으로 들어

1Password Shell Plugins로 CLI 인증 관리하기

1Password Shell Plugins로 CLI 인증 관리하기

CLI 도구를 쓰다 보면 인증 토큰을 어디에 둘지가 늘 고민입니다. GitHub CLI는 GH_TOKEN, Cloudflare Wrangler는 CLOUDFLARE_API_TOKEN 같은 환경 변수를 지원하는데요. 편하다고 셸 설정 파일이나 .env 파일에 토큰을 넣어두면 어느 순간 평문 시크릿이 로컬 디스크 여기저기에 흩어집니다. 1Password에는 이런 문제를 줄여주는 Shell Plugins 기능이 있습니다. CLI가 인증 정보를 필요로 할 때 1Password가 토큰을 꺼내 환경 변수로 주입하고, 사용자는 지문이나 Appl

Discord