← Blog
AI온디바이스 AI성능 최적화React QueryCloudflare D1

브라우저 AI, 맡길 일과 기다릴 일을 줄였어요

·8분 읽기

같은 광고인데, 추천이 달라져도 괜찮을까요?

AdsManager는 광고 성과를 확인하고 예산을 조정하는 대시보드예요. 브라우저에서 실행되는 AI로 대화와 추천 기능을 붙였는데, 만들다 보니 먼저 풀어야 할 문제가 보였어요. 같은 데이터를 보여줘도 AI가 추천하는 캠페인이나 조정 비율은 달라질 수 있었거든요.

예산을 실제로 바꿀 때는 코드로 조정 폭을 1~30%로 제한하고 있었어요. 하지만 실행 범위를 제한하는 것만으로는 “왜 이 캠페인을 이만큼 바꿔야 하는지”까지 일관되게 만들 수 없었어요. 그래서 광고 수익률과 추세를 보고 캠페인, 조정 방식, 비율, 근거 수치를 결정하는 일을 코드로 옮겨봤어요. AI에는 이미 정해진 결정을 전달하고 제목과 설명만 쓰게 했고요.

이렇게 나누니 같은 데이터에는 같은 판단이 나왔어요. AI가 응답을 빠뜨리거나 잘못된 값을 보내도 해당 항목만 기본 문구로 바꿔 추천을 보여줄 수 있었어요. 당시 테스트 55개와 타입 검사·린트를 통과했고, 브라우저 자동화로 실제 캠페인명과 비율, 근거 수치가 표시되는 것도 확인했어요.

대신 정해진 규칙 밖의 판단은 추천에 담기지 않아요. 이 제약은 감수하고, 코드가 판단 기준을 관리하고 AI는 결정된 내용을 설명하도록 역할을 나눴어요.

구조 살펴보기판단과 문장을 분리하는 과정구조를 설명하기 위한 예시예요. 10%는 실제 추천 기준이나 측정값이 아니에요.

처음

  1. 캠페인 데이터성과와 지출을 전달해요
  2. AI가 판단 + 설명대상과 비율도 함께 정해요
  3. 적용 단계에서 제한조정 폭만 1~30%로 확인해요

역할을 나눈 뒤

  1. 코드가 판단예: A 캠페인 · 예산 10% 축소
  2. AI가 설명결정된 수치와 근거를 문장으로 옮겨요
  3. 추천 표시응답이 잘못되면 기본 문구를 써요

그런데 이 문장도 꼭 AI가 써야 할까요?

판단을 옮기고 나서도 기다림은 남아 있었어요. 주간 분석처럼 답의 경우의 수가 정해진 곳에서도 AI가 문장을 새로 쓰고 있었거든요. 당시 모델의 한국어 지원 제약 때문에 영어 생성과 번역을 거쳤고, 그 과정에서 광고 이름이나 숫자가 바뀔 위험도 있었어요.

처음에는 프롬프트를 더 제한하거나, 이름과 숫자를 임시 토큰으로 바꿨다가 복원하는 방법을 검토했어요. 그런데 코드가 이미 답을 알고 있는 문장이라면 생성과 번역을 아예 거치지 않아도 되겠더라고요. 그래서 주간 분석과 캠페인 설정 안내는 코드가 문장까지 완성하도록 바꿨어요. 자유롭게 묻고 답하는 채팅에는 AI와 번역을 유지하되, 광고 이름을 토큰으로 보호하고 응답 뒤에 복원했어요.

그 결과 주간 분석과 설정 안내는 AI가 없어도 동작하고, 같은 상황에는 같은 문장을 보여주게 됐어요. 이 두 기능에서는 이름과 숫자가 생성이나 번역 중 달라지는 과정도 없어졌어요. 앞에서 나눴던 “코드가 판단하고 AI가 설명하는 구조”에서 한 단계 더 줄인 셈이에요.

표현이 조금 반복되고 새로운 상황의 문구를 직접 관리해야 한다는 점은 받아들이기로 했어요. 정해진 안내는 코드로 완성하고, 자유로운 대화가 필요한 채팅에만 AI와 번역을 쓰도록 정리했어요.

구조 살펴보기최종적으로 AI가 남은 곳정해진 안내는 코드로 완성하고, 자유로운 대화에 AI를 사용해요.

주간 분석 · 설정 안내

  1. 성과 데이터수치와 상태를 읽어요
  2. 코드의 규칙 + 문구판단과 안내를 함께 만들어요
  3. 바로 표시AI와 번역을 기다리지 않아요

자유로운 채팅

  1. 질문 + 필요한 정보광고 이름은 토큰으로 보호해요
  2. 브라우저 AI + 번역질문에 맞는 답변을 만들어요
  3. 이름 복원 + 표시실패하면 기본 답변을 써요

대화만 하려는데, 왜 다른 AI까지 기다려야 하죠?

처음에는 대화, 캠페인 초안, 추적 규칙을 위한 AI 세션 3개와 번역기 2개를 함께 준비했어요. 다섯 자원이 모두 준비돼야 사용 가능으로 표시했기 때문에, 대화만 하려는 사람도 가장 느린 기능을 기다려야 했어요.

먼저 기능별 준비 상태를 나누고 대화와 번역기를 먼저 준비해봤어요. 초안과 규칙은 기본 세션을 복제해서 쓰고 작업이 끝나면 해제했어요. 대화만 문맥을 유지하고, 요청별 대기는 15초로 제한했어요. 초기 비교에서는 초안 준비가 2.70초에서 1.10초로, 규칙 준비가 3.32초에서 0.90초로 줄었어요.

모든 측정이 좋아진 건 아니었어요. 대화 세션 생성이 22.47초까지 늘어난 회차도 있었거든요. 다음 측정에서는 재현되지 않았고 대화 사용 가능 1.40초, 초안 생성 491ms, 규칙 생성 599ms, 전체 준비 2.50초를 기록했어요. 다만 한 번 재현되지 않았다고 지연 원인까지 해결했다고 볼 수는 없었어요.

이어서 대화를 열어야만 다른 기능을 예열하던 조건도 바꿨어요. 어떤 기능이든 준비되면 나머지를 준비하고, 첫 클릭이나 키 입력에도 다운로드를 재시도하게 했어요. 이 변경은 당시 테스트 77개와 빌드를 통과했어요.

여기까지 바꾸고 보니 더 근본적인 비용이 남아 있었어요. 사용자가 방문하지 않은 화면의 AI도 결국 모두 준비하고 있었거든요. 그래서 마지막에는 사이트 진입 때 대화와 번역기만 준비하고, 초안과 규칙은 해당 화면을 열 때 준비하도록 바꿨어요. 다시 방문하면 기본 세션을 재사용하고, 번역기는 함께 쓰도록 했어요.

기능별 분리 직후에는 채팅 사용 시점이 3.33초에서 약 2.55초로 줄었어요. 이후 화면별 준비 방식에서는 방문하지 않은 화면의 세션을 만들지 않는 동작을 확인했어요. 당시 전체 테스트 84개와 빌드를 통과했고, 런타임 테스트 29개로 생성·재사용·건너뛰기·재시도를 확인했어요.

대신 초안이나 추적 설정을 처음 열면 그때 준비를 기다려야 해요. 이 기다림은 감수하고, 각 화면에 필요한 세션을 진입 시점에 준비하고 재방문 때는 재사용하도록 바꿨어요.

구조 살펴보기화면을 열 때 필요한 세션만 준비해요준비 시점을 비교한 구조도예요. 화살표는 처리 순서를 뜻하며 소요 시간을 나타내지는 않아요. 번역기는 여러 기능이 공유해요.

이전 · 사이트 진입

  1. 세 기능 + 번역기 준비방문하지 않을 화면도 포함해요
  2. 모두 완료될 때까지 대기가장 느린 자원이 기준이에요
  3. 채팅 사용 가능대화 준비가 끝나도 기다려요

변경 · 사이트 진입

  1. 대화 + 번역기 준비채팅에 필요한 자원만 만들어요
  2. 채팅 사용 가능초안과 규칙은 기다리지 않아요

변경 · 다른 화면 진입

  1. 초안 또는 추적 설정 열기필요한 기능이 정해져요
  2. 해당 세션만 준비재방문하면 기본 세션을 재사용해요
  3. 작업 후 복제 세션 해제일회성 작업의 자원을 정리해요

바뀌지 않은 정보를 매번 다시 알려주고 있었어요

준비 시간을 줄여도 대화가 길어지면 또 다른 기다림이 생겼어요. 캠페인이 하나도 바뀌지 않았는데 전체 정보를 매번 AI에게 다시 보내고 있었거든요. 처리량이 쌓이면서 12초 안에 답을 끝내지 못하고 기본 답변으로 대체되는 경우가 있었어요.

그래서 데이터가 바뀌었거나 마지막 전송 후 4~5턴이 지났을 때만 전체 정보를 다시 보내도록 바꿨어요. 예를 들어 대화 중 예산을 조정했다면 바로 새 정보를 보내고, 같은 광고에 대해 이어 묻는 동안에는 기존 문맥을 활용하는 방식이에요. 마지막 전송 상태는 채팅창이 아니라 AI 세션에 저장해서 창을 닫았다 열어도 유지했어요.

모델이 앞선 문맥을 기억하는 데 의존한다는 점은 감수하기로 했어요. 전체 정보를 매번 보내던 구조를 데이터 변경 여부와 대화 턴 수에 따라 다시 보내는 방식으로 수정했어요.

캠페인 2,008건을 넣으니 화면 쪽 병목도 보였어요

AI에 주는 정보만 줄이면 끝날 줄 알았는데, 데이터가 늘어나자 홈 화면도 무거워졌어요. 기존 8건에 테스트 캠페인 2,000건을 더해보니 목록 응답에 모든 캠페인의 14일치 이력이 담겨 있었어요. 홈과 AI가 모두 이 배열을 받아 계산하고 있었고요.

홈에 필요한 계산을 서버로 옮기고 요약값만 보내봤어요. 목록 조회에는 커서 기반 페이지 나누기 옵션을 추가하고, AI에는 최근 7일 지출 상위 20개만 전달했어요. 캠페인이 8건에서 2,008건으로 늘 때 전체 응답은 9.4KB에서 3.3MB로 커졌지만, 홈 요약은 13.4KB에서 38KB로 늘어나는 데 그쳤어요. 당시 테스트 55개와 타입 검사·린트도 통과했어요.

대신 AI에 전달하지 않은 캠페인은 대화 문맥에서 빠져요. 모든 광고를 한 번에 설명하는 범위는 내려놓기로 했어요. 또 서버가 요약을 만들기 위해 전체 행을 조회하는 비용과, 페이지 나누기 옵션 없이 호출하는 목록의 전송 비용은 아직 남아 있어요.

목록 화면에서도 비슷한 문제가 있었어요. 오류는 없는데 2,008건을 한 번에 그리느라 빈 화면처럼 보였거든요. 처음에는 30건만 보여주고 더 보기로 30건씩 늘리도록 바꿨더니, 클릭 후 반응 시간이 1,076ms에서 209ms로 줄었어요. 렌더링된 본문도 111,602자에서 1,657자로 줄었고요.

전체를 보려면 더 보기를 반복해야 하는 불편은 감수했어요. 데이터를 받는 방식은 유지하면서, 화면에는 처음 30건을 그리고 사용자가 더 보기를 누를 때마다 30건씩 추가하도록 수정했어요.

방금 본 데이터라면 잠깐은 다시 써도 되겠더라고요

마지막으로 메뉴를 오갈 때 같은 데이터를 계속 요청하고 있었어요. 캐시는 있었지만 최신으로 취급하는 시간이 0이라, 홈과 캠페인을 두 번씩 방문하면 요약과 목록도 각각 두 번 요청했어요.

그래서 캠페인 목록·요약·상세는 30초 동안 캐시를 재사용하도록 했어요. 화면을 미리 채우는 데모 데이터와 실제 응답 캐시는 구분해서 첫 조회가 바로 실행되게 했고, 캠페인을 수정하면 목록·요약·해당 상세 캐시만 무효화했어요.

같은 2,008건과 메뉴 이동 조건으로 다시 보니 요약과 목록 요청이 각각 2회에서 1회로 줄었어요. 응답 본문 합계도 약 6.75MB에서 3.37MB로 줄었고요. 두 번째 방문에는 추가 요청이 없었고, 31초 뒤에는 다시 조회했어요. 당시 캐시 테스트 18개를 포함한 전체 102개 테스트와 타입 검사·빌드를 통과했어요.

다른 곳에서 수정한 데이터가 캐시를 사용하는 동안 바로 보이지 않을 수 있다는 점은 받아들이기로 했어요. 첫 조회 방식은 유지하고, 메뉴를 오갈 때는 30초 동안 캐시를 재사용하되 캠페인을 수정하면 관련 캐시를 갱신하도록 정리했어요.