Development

[Jira API] v2 평문으로 읽으면 LLM 토큰을 아낄 수 있을까?

江多林 2026. 8. 28. 17:37

v3 ADF가 아무래도 무거워 보여서 v2 평문으로 읽어봤다.
raw 크기는 정말 절반쯤이었다.
그런데 체크박스의 완료 상태까지 같이 사라지고 있었다. 😑


3줄 요약

Jira description을 v2 평문으로 읽으면 v3 ADF보다 raw payload가 대략 절반 정도로 줄어드는 것으로 보인다.
그런데 우리가 쓰는 taskItem의 DONE/TODO 상태도 같이 사라진다.
그래서 지금은 v3를 버리기보다, 필요한 곳에 v2를 섞어 쓰는 쪽이 적당하지 않을까 생각 중.

 


발단 — ADF가 좀 무겁지 않나?

LLM Agent가 Jira를 제법 많이 읽기 시작했다.

이슈 하나 읽는 정도야 별일 아닌데,
프로젝트 전체를 훑고, description 읽고, 하위 이슈 보고, 다시 comment를 읽다 보면...

결국 이것도 다 토큰이다.

Jira REST API v3의 description은 ADF(Atlassian Document Format) JSON으로 내려온다.

대충 이런 느낌.

{
  "type": "doc",
  "version": 1,
  "content": [
    {
      "type": "paragraph",
      "content": [
        {
          "type": "text",
          "text": "어쩌고 저쩌고..."
        }
      ]
    }
  ]
}

사람 눈에는 그냥 한 줄인데
꽤 많은 구조물이 따라온다.

그러다가 아침 운영 스레드에서 이런 질문이 나왔다.

"ADF를 사용하지 않고 v2 API를 사용하는 것이 토큰과 기능을 아낄 수 있지 않을까?"

음...

그럴 것 같은데?

v2에서는 description이 평문에 가까운 형태로 내려오니
JSON 구조가 왕창 붙는 v3보다 가벼울 가능성이 높아 보였다.

다만 하나가 걸렸다.

우리는 Jira description에서 체크박스를 꽤 많이 쓴다.

그리고 이 체크박스를 단순한 장식이 아니라
Agent의 작업 완료 여부를 판단하는 데에도 쓰고 있다.

그래서 바로 바꿔보기보다는 몇 가지를 먼저 재보기로 했다.

  1. 체크박스 상태가 v2에서도 살아 있는가?
  2. v3에서만 볼 수 있는 구조가 실제로 얼마나 있는가?
  3. 그래서 크기는 진짜 얼마나 차이 나는가?

1. 체크박스부터 확인

같은 이슈를 v2와 v3로 각각 읽어봤다.

결과는 꽤 명확했다.

이슈v3 ADFv2 평문

MSVC-2 taskItem 4개, DONE ×4 그냥 * 불릿
RCS-19 taskItem 3개, DONE ×3 상태 흔적 없음
HERPIPE-43 taskItem 5개, TODO ×5 상태 흔적 없음

ADF에는 이런 정보가 있다.

taskItem
  state: DONE

taskItem
  state: TODO

그런데 v2로 읽으면 그냥 목록이다.

완료했는지, 아직 안 했는지...

모른다. ㅡㅡ;;

처음에는

뭐... 체크박스 모양 하나 없어지는 거 아닌가?

싶었는데,

우리 쪽에서는 조금 다르다.

워커 규칙 중에 대략 이런 것이 있다.

미완료 체크박스가 남아 있으면
In Review로 넘기지 않는다.

즉, description 안의 DONE/TODO를 읽어서
작업이 인수조건을 만족했는지 확인하는 구조.

그러니까 v2로 전부 바꾸면,

토큰은 좀 줄일 수 있을지 모르지만
완료와 미완료를 구분하던 근거도 같이 없어지는 셈이다.

음...

이건 좀 크다.


2. 그렇다면 이런 ADF 구조를 얼마나 쓰고 있을까?

몇 개 보고 판단하기에는 애매해서
운영 중인 프로젝트 description을 한번 전부 훑어봤다.

대상은

  • RCS
  • HERPIPE
  • MSVC

세 프로젝트.

ADF node를 종류별로 세어보니 대략 이랬다.

노드 개수 v2에서
taskItem 280 상태 소실
taskList 70 일반 목록 형태
inlineCard 298 URL 문자열로 남음
table 1곳 / 9 cell 평문으로 풀림
codeBlock 3 텍스트로 대체로 남음

영향받는 이슈는 69건 정도.

생각보다 ADF 구조를 많이 쓰고 있었다.

그런데 하나씩 보니
ADF 구조가 없어지는 것 자체가 모두 문제는 아니었다.

예를 들어 inlineCard.

Jira에서 URL 붙이면 예쁘게 카드처럼 나오는 그것.

v3에서는

{
  "type": "inlineCard",
  "attrs": {
    "url": "https://..."
  }
}

이런 구조인데,

v2에서는 대략 wiki 스타일 문자열로 내려온다.

[https://...|https://...|smart-link]

예쁘지는 않지만...

URL은 있다.

codeBlock도 비슷했다.

구조는 없어져도 내용은 대체로 남았다.

table도 평문으로 풀어쓰면
보기는 좀 망가지지만 내용을 못 읽는 수준은 아니었다.

이렇게 놓고 보면,

우리 환경에서는 ADF 구조 중 실제로 신경 쓰이는 것은
아무래도 taskItem의 state 쪽이었다.


3. 그래서 얼마나 작아지는데?

이제 처음 질문으로 돌아갔다.

v2로 읽으면 정말 토큰을 아낄까?

자동링크 inlineCard가 많이 들어 있는 이슈 3개를 골라서
v2/v3 원문 크기를 비교해봤다.

이슈 inlineCard v2 raw v3 raw 비율
아침 보고 77 9,997B 20,006B 2.0x
이슈 A 60 8,151B 16,301B 2.0x
이슈 B 31 4,383B 9,166B 2.1x

오...

정말 두 배네?

처음 직관은 대략 맞아 보였다.

다만 여기서 하나 조심할 것이 있었다.

이 숫자는 LLM tokenizer로 센 token 수가 아니라 raw byte 크기다.

그리고 우리 Agent가 Jira API 응답 JSON을 그대로 LLM에 집어넣는 것도 아니다.

중간에 텍스트를 추출한다.

즉,

Jira API
   ↓
ADF JSON
   ↓
text extractor
   ↓
LLM context

이런 구조.

그러니까

raw JSON이 2배라고 해서 실제 LLM token도 정확히 2배라고 보기는 어렵다.

오히려 우리가 궁금해야 할 것은

API에서 몇 byte 받았나?

보다

최종적으로 LLM에게 몇 글자/토큰을 넘겼나?

쪽에 가까웠다.

처음 질문이 살짝 바뀌었다.


4. 그런데 이상한 숫자 하나

텍스트 추출 결과도 비교해봤다.

어떤 이슈 하나가

v2 추출: 21,605자
v3 추출:  3,764자

...

이건 또 너무 차이가 크다.

ADF가 아무리 구조적이어도
본문 내용 자체가 여섯 배씩 달라질 이유는 없어 보였다.

확인해보니 다른 문제가 있었다.

우리 ADF → text 추출기가 inlineCard의 URL을 버리고 있었다. 😑

즉,

{
  "type": "inlineCard",
  "attrs": {
    "url": "https://..."
  }
}

를 만나면

URL을 텍스트 결과에 넣지 않고 그냥 지나가고 있었다.

반면 v2에서는 URL 자체가 문자열이니까
그대로 남는다.

처음에는

v2가 텍스트 보존을 더 잘하네?

라고 생각할 수도 있었는데,

조금 더 까보니 Jira API의 문제가 아니라
우리 코드의 문제에 가까웠다.

이건 v3를 그대로 쓰면서도 고칠 수 있다.

ADF extractor에

inlineCard → attrs.url 포함

정도만 넣어주면 될 듯.

토큰 줄여보겠다고 시작했는데
엉뚱하게 자체 버그 하나를 주웠다. 💪


5. 그래서 v2로 갈아탈까?

여기까지 해보고 나니
처음 질문에 그냥 Yes/No로 답하기가 조금 애매해졌다.

v2가 좋아 보이는 부분

단순 조회라면 아무래도 가볍다.

특히 URL 카드가 많이 들어 있는 description은
raw 기준으로 절반 정도까지 줄어드는 경우가 있었다.

다음 같은 작업이라면 꽤 쓸 만할 것 같다.

  • 이슈 목록 읽기
  • description 요약
  • 링크 수집
  • 사람이 보기 위한 리포트
  • task 상태와 무관한 검색

이런 경우에는

굳이 ADF 전체 구조를 받을 필요가 있을까?

싶다.

그런데 v3가 필요한 부분

반대로,

  • taskItem DONE/TODO 확인
  • 인수조건 검사
  • 작업 완료 판정
  • description 수정
  • 구조를 유지해야 하는 작업

같은 경로는 아직 v3 ADF가 필요해 보인다.

우리 쪽에서는 특히

미완료 체크박스 존재
       ↓
In Review 전이 금지

가 있기 때문에,

taskItem state를 포기하면서까지
v2로 단순화할 이유는 크지 않아 보였다.


지금 생각은 — 전환보다는 병행

그래서 지금 생각은 대략 이쪽이다.

                    Jira
                      │
             ┌────────┴────────┐
             │                 │
       상태 필요 없음          상태 필요
             │                 │
          API v2            API v3
             평문               ADF
             │                 │
        가볍게 읽기         구조까지 읽기

SDK에 예를 들어

issue_text_v2(key)

같은 가벼운 helper를 하나 두고,

상태가 필요 없는 읽기에서만 v2를 사용하는 방식.

기존 v3 경로를 없애기보다는
읽는 목적에 따라 하나 더 만들어두는 것이 조금 편하지 않을까 싶다.

아직 실제 전체 Agent workload를 tokenizer 기준으로 비교해본 것은 아니어서
v2 병행의 최종 절감폭이 얼마나 될지는 조금 더 봐야 할 것 같다.

raw에서는 2배 차이가 보였지만,

실제 LLM 앞단에서는 extractor가 있으니까.


이번 삽질에서 얻은 것 몇 가지

1. 가벼운 포맷이 항상 싼 것은 아닌 듯

처음에는 단순했다.

ADF JSON 큼
     ↓
v2 text 작음
     ↓
토큰 절약

그런데 중간에 state가 있었다.

ADF JSON 큼
     ↓
v2 text 작음
     ↓
task state도 없어짐
     ↓
우리 Worker는 완료 여부 모름

크기만 보면 v2가 싸다.

그런데 빠지는 정보가
업무 규칙에 쓰이는 값이라면 계산이 달라질 수 있다.


2. raw 크기보다 LLM 직전의 크기를 보는 것이 맞을 듯

이번 실측에서 raw payload는 꽤 예쁘게 2배 차이가 났다.

하지만 우리 구조에서는 API 결과가 바로 LLM으로 가지 않는다.

API → Parser → Extractor → Prompt → LLM

그러니까 토큰 최적화를 하려면
앞단 API보다 Prompt 직전을 재는 것이 조금 더 맞을 듯하다.

이건 다음에 tokenizer까지 붙여서 한번 더 재볼 생각.


3. 상태는 생각보다 비싸다

DONE/TODO라고 해봐야 값 하나다.

그런데 그 한 값이

작업 완료?
검수 가능?
In Review 이동?

을 결정한다.

표현 정보 하나쯤으로 생각했던 것이
우리 파이프라인에서는 사실상 작은 상태 DB 역할을 하고 있었다.

이걸 없애려면 토큰보다 더 많은 것을 다시 만들어야 할 수도 있다.


4. 전수로 세어보니 생각이 좀 편해졌다

처음에는

taskItem 많이 쓰나?
inlineCard가 그렇게 많은가?

정도의 감이었다.

직접 세어보니

taskItem     280
inlineCard   298
영향 이슈     69

정도.

숫자를 보고 나니
뭘 중요하게 봐야 할지가 조금 명확해졌다.

아마 이런 건 몇 건 샘플보다
한번 긁어서 세어보는 게 빠른 듯.


일단 여기까지

처음 질문은 이것이었다.

Jira v2 평문 API로 읽으면 토큰을 아낄 수 있을까?

지금까지 본 것으로는,

아낄 여지는 꽤 있어 보인다.

특히 ADF raw payload 자체는
링크가 많은 이슈에서 v2 대비 약 2배 정도 컸다.

그런데 그것만 보고 전체를 v2로 내리기에는
taskItem 상태가 빠지는 비용도 생각보다 컸다.

그래서 당장은

v3를 유지하면서 v2를 필요한 읽기에만 섞어보는 쪽.

그리고 inlineCard URL을 버리던 우리 extractor부터 고치는 걸로. 😑

한 줄짜리 질문이었는데...

또 파다 보니 API 선택이 아니라
데이터의 어느 상태를 어디까지 보존할 것인가 쪽 이야기까지 왔다.

뭐... 늘 그렇듯.

토큰 조금 아껴보려다가
설계 하나 더 생겼다. 💪