Jira, Confluence의 Rest API 를 더 효율적으로 사용하는 방법이 궁금하신가요?

안녕하세요. 오픈소스컨설팅에서 Atlassian Addon 제품 개발 및 운영을 담당하고 있는 App-Biz팀의 윤보영 프로입니다. Atlassian Add-on 개발 및 운영 과정에서 Jira와 Confluence의 Rest API를 자주 활용하게 되는데, 이를 더 효율적으로 사용하는 방법을 고민하던 중, 그 내용을 블로그로 공유하고자 이 글을 작성하게 되었습니다.


Atlassian Cloud Rest API는 Jira, Confluence, Bitbucket 등의 제품과 통합을 가능하게 하며, 이를 통해 클라우드 환경에서 주요 기능을 프로그래밍 방식으로 제어하고 자동화할 수 있습니다. API를 활용하면 이슈 관리, 페이지 생성, Code Repository 작업 등을 자동으로 처리하여 협업과 생산성을 높일 수 할 수 있습니다.


Atlassian Cloud Rest API를 사용할 때 마주하는 일반적인 문제들은 아래와 같았습니다.

  • Request 수가 많아질 때 발생하는 성능 문제
  • API Request Limit를 초과할 때의 문제
  • API 응답 처리 시간 지연
  • 비효율적인 Request/Response 패턴

위의 문제를 아래의 방법으로 해결하고자 합니다.

  • Rest API 요청 수 줄이기
  • Pagination을 제대로 사용하기
  • 오류 처리(Error Handling) 잘하기
  • HTTP Persistent Connection Pool 사용하기
  • 대안을 사용하기 : GraphQL, Webhooks, Forge


Rest API 요청 수 줄이기

Expand Query Parameters 사용하기
일부 Atlssian Rest API는 Expand Query Paramater를 지원합니다.
각 API 요청에 expand 매개변수를 사용해 필요한 추가 데이터를 포함시킬 수 있습니다.
예를 들어, “/rest/api/3/issue/{issueIdOrKey}?expand=summary,comment은 이슈 검색 결과에 이슈 제목과 댓글 내용을 포함하여 결과값을 반환합니다.

(GET) /rest/api/3/issue/{issueIdOrKey}?expand=summary,comment

관련 문서 : https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/#expansion

Rest API 요청 시에 데이터를 제한하기
위의 경우에서는 가져오는 데이터를 확장하였지만, 이와 반대로 때때로 가지고 올 데이터의 수를 제한하는 것이 API 응답 속도를 개선할 수 있습니다.
예를 들어, 아래의 Rest API는 Jira의 이슈를 검색하는 API입니다. 검색하고자 하는 이슈에 필요한 데이터만 가지고 오도록 제한하여 API 응답 속도를 빠르게 할 수 있습니다.

(GET) /rest/api/2/search?jql='project=HSP'
&fields=created,updated&properties=prop1,prop2

아래의 예시 처럼 모든 데이터를 가지고 오도록 호출하면, 성능 이슈를 야기할 수 있습니다.

(GET) /rest/api/2/issue/HSP-111?properties=*all

이슈 검색 Rest API 문서(V2): https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-issue-search/#api-rest-api-2-search-get

>> Atlassian Rest API 베타 버전에 대해서 (클릭하여 상세정보보기)

Atlassian Jira Rest API는 Version 2와 Version 3로 나누어 지원합니다. Version 3는 최신화된 Resposne 형식(ADF)과 더 많은 기능을 지원합니다.
하지만 현재는 베타 버전이기 때문에 Response 형식과 기능이 일부 변경될 수 있습니다.

이슈 검색 Rest API 문서(V3 Beta): https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-search/#api-group-issue-search

Bulk Rest API 사용하기
Atlassian Bulk REST API는 Atlassian의 REST API 중에서 다수의 요청을 한 번에 처리할 수 있는 기능을 제공하는 API입니다. 주로 여러 개의 엔티티(예: 이슈, 사용자, 댓글 등)를 처리해야 할 때, 각 엔티티에 대한 요청을 하나씩 보내는 대신 한 번에 묶어서 처리할 수 있습니다.
기존의 API를 사용하여 이슈를 여러개를 수정해야 하는 경우, 이슈 하나당 API를 한번씩 요청하여 수정해야 합니다.
 이를 통해 요청 수를 줄이고 성능을 향상시키며, API 사용 한도(rate limit)에 걸릴 위험을 최소화할 수 있습니다.

관련 문서 : https://developer.atlassian.com/cloud/jira/platform/bulk-operation-additional-examples-and-faqs/



Pagination을 제대로 사용하기

Atlassian REST API의 Pagination 기능은 많은 데이터를 처리할 때, 응답 데이터를 한 번에 모두 받지 않고 일정한 크기로 나누어 받아오는 방법입니다. 이 기능은 API를 사용할 때 서버 부하를 줄이고, 클라이언트 측에서 데이터를 더 효율적으로 처리할 수 있게 해줍니다

기본적으로 페이지 크기를 지정하는 “maxResults”와 시작 위치를 지정하는 “startAt” 파라미터로 Pagination 기능을 사용합니다. 예시는 아래와 같습니다.

GET /rest/api/2/search?jql=...&startAt=101&maxResults=100

Parallel Pagination(병렬 페이지네이션)
가지고 와야 하는 데이터가 많은 경우 Parallel Pagination을 구현하여 API를 호출할 수 있습니다. Parallel Pagination은 페이징된 API 데이터를 병렬로 동시에 요청하여 성능을 최적화하는 방법입니다.
이 방식은 각 요청이 순차적으로 처리되는 것이 아니라, 여러 요청을 한 번에 실행하여 처리 속도를 높일 수 있습니다. 특히 데이터 양이 많을 때, 병렬 요청을 통해 전체 데이터를 더 빠르게 가져올 수 있는 장점이 있습니다.
하지만 해당 방식은 데이터의 크기나 API의 제한을 고려해 적절히 조정해야 합니다.

자바스크립트로 Parallel Pagination를 구현한 예시입니다.

async function fetchPage(startAt) {
  const response = await fetch(`https://your-jira-site.atlassian.net/rest/api/2/search?jql=project=TEST&startAt=${startAt}&maxResults=100`, {
    method: 'GET',
    headers: {
      'Authorization': `Basic your_encoded_credentials`,
      'Content-Type': 'application/json'
    }
  });

  return response.json();
}

async function fetchAllPages() {
  const requests = [];
  const totalPages = 7; // 가져올 페이지 수 예시

  for (let i = 0; i < totalPages; i++) {
    const startAt = i * 100;
    requests.push(fetchPage(startAt));
  }

  // 모든 요청을 병렬로 실행
  const results = await Promise.all(requests);
  console.log(results);  // 모든 페이지의 결과 출력
}

fetchAllPages();

데이터 일관성 유지하기
Pagination을 사용할 때 데이터 일관성을 유지하는 것이 매우 중요합니다. 특히 데이터가 많고 동적으로 변할 수 있는 경우, 데이터를 나누어 받아오면서도 정확한 순서와 일관성을 유지하는 것이 필수적입니다.
예를 들어, Jira Project의 이슈를 가장 최근에 수정된 순으로 정렬하여 가져오는 API를 호출하였습니다. Pagination을 이용해 해당 API를 여러번 호출하던 중 이슈가 수정되면, 각 페이지에 표시되는 이슈의 순서가 바뀌면서, 결과에서 이슈들이 누락될 수 있습니다.


(GET) /rest/api/2/search?jql='PROJECT IN HBD ORDER BY updated DESC'
  1. 데이터 정렬 시, 고유한 값 사용하기
    Atlassian Rest API를 사용할 때는 언제든지 변동될 수 있는 값이 아닌 고유한 값(이슈키, 타임스탬프)을 기반으로 페이징을 하면 이슈 수정, 생성시에도 순서 변동없이 데이터를 호출할 수 있습니다.
  2. Cursor-based Pagination(커서 기반 페이징) 사용하기
    또다른 방법으로는 Cursor-based Pagination(커서 기반 페이징)을 통해 데이터를 가지고 올 수 있습니다.
    커서 기반 페이징은 각 페이지의 마지막 데이터를 기준으로 다음 페이지를 불러오는 방법입니다.
    이 방식은 데이터가 변경되거나 삽입되더라도, 커서를 기준으로 데이터가 정확하게 이어지므로 일관성을 유지할 수 있습니다.

Pagination(페이지네이션)을 사용 시에는 경우에 따라서 데이터 일관성이 깨질 수 있다는 것을 염두에 두고, 상황에 맞는 방법을 사용하여 최대한 데이터 일관성을 유지하도록 하는 것이 중요합니다.

관련 문서 >> https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/#pagination


오류 처리(Error Handling) 잘하기

Atlassian Rest API를 사용하다 보면 정상적인 데이터가 아닌 아래와 같은 에러 코드들이 응답될 수 있습니다.



에러 코드(Error Code)
에러 메세지(Error Message)발생 원인
400Bad RequetsRest API 호출 시에 잘못된 요청을 보냄
401Unauthorized유효하지 않은 인증정보로 API를 호출함
403Fobidden요청한 인증정보의 사용자나 앱이 API 호출 권한이 없음
404Not found요청한 Reat API를 찾을 수 없음
429Too Many Request요청 제한을 초과함
500Internal Server ErrorAtlassian 제품 오류
503Service UnavailableAtlassian 제품 오류. 요청 속도 제한을 초과했을 때 발생할 수 있습니다.
504Gateway Timeout요청한 쪽에 너무 오랜시간동안 응답을 받지 못해 요청이 취소됨

위의 코드 중 429, 500, 503, 504는 일시적인 에러, 똑같은 요청을 다시 시도하면 정상적인 데이터를 받은 가능성도 있습니다. 특히 429 에러는 Atlassian이 정해둔 요청 제한을 넘어섰을 때, 반환되는 에러입니다. 따라서 일정 시간 이후 재시도하도록 예외처리하는 것이 일반적입니다.

아래의 코드는 429 에러 발생시, 응답에 포함된 Retry-After 헤더에 명시된 대로 일정 시간 후에 재시도하는 만든 예시 코드입니다. 자바스크립트로 작성되었습니다.

async function makeRequestWithRetry(url, options, maxRetries = 5) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch(url, options);

      // If response is successful (status 200), return the response
      if (response.ok) {
        return await response.json();
      }

      // If response is 429 (Too Many Requests), handle the retry
      if (response.status === 429) {
        const retryAfter = response.headers.get('Retry-After');
        const retryDelay = retryAfter ? parseInt(retryAfter, 10) * 1000 : 1000; // Use Retry-After header, or default to 1 second

        console.warn(`Rate limit exceeded. Retrying after ${retryDelay / 1000} seconds... (Attempt ${attempt} of ${maxRetries})`);

        // Wait for the specified retry delay before retrying
        await new Promise(resolve => setTimeout(resolve, retryDelay));
      } else {
        // If it's not a 429 error, throw the error
        throw new Error(`Request failed with status ${response.status}`);
      }
    } catch (error) {
      if (attempt === maxRetries) {
        console.error(`Max retries reached. Request failed: ${error.message}`);
        throw error; // Re-throw the error if maximum retries are reached
      }
    }
  }
}

// Example usage
const url = 'https://api.atlassian.com/...';  // Replace with your Atlassian API endpoint
const options = {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
    'Content-Type': 'application/json'
  }
};

makeRequestWithRetry(url, options)
  .then(data => console.log('API Response:', data))
  .catch(error => console.error('Final Error:', error));


관련 문서 >> https://developer.atlassian.com/cloud/jira/platform/rate-limiting/



HTTP Persistent Connection Pool 사용하기


HTTP Persistent Connection Pool은 여러 HTTP 요청을 처리할 때 동일한 연결을 재사용하여 성능을 향상시키는 방법입니다. 새로운 요청마다 연결을 생성하고 닫는 대신, 한 번 연결을 설정하면 여러 요청에 걸쳐 유지해 오버헤드를 줄일 수 있습니다. 이를 통해 요청 처리 속도가 빨라지고, 서버 및 네트워크 자원을 절약할 수 있습니다. 지속 연결 풀은 특히 빈번한 API 호출이나 대규모 트래픽을 효율적으로 관리하는 데 유용합니다.

자바스크립트에서는 fetchaxios 같은 라이브러리를 사용할 때 이와 같은 Persistent Connection을 활용할 수 있습니다. 예를 들어, 아래는 axios로 HTTP 요청을 보내는 코드에서 keep-alive 옵션을 사용하는 방법입니다

const axios = require('axios');
const http = require('http');

// HTTP Agent를 사용하여 keep-alive 설정
const agent = new http.Agent({ keepAlive: true, maxSockets: 10 });

axios.get('https://api.example.com/data', {
    httpAgent: agent, // HTTP Keep-Alive 옵션 설정
})
.then(response => {
    console.log('첫 번째 응답:', response.data);
    return axios.get('https://api.example.com/data2', {
        httpAgent: agent, // 동일한 연결을 사용
    });
})
.then(response => {
    console.log('두 번째 응답:', response.data);
})
.catch(error => {
    console.error('요청 중 오류 발생:', error);
});

대안을 사용하기 : GraphQL, Webhooks

Atlassian에서는 REST API 외에도 GraphQL과 Webhook을 지원하여 다양한 방식으로 시스템과 통신하고 상호작용할 수 있습니다.
GraphQL은 API 호출을 더 유연하게 할 수 있도록 설계된 쿼리 언어입니다. REST API와 달리, 클라이언트는 필요한 데이터만 요청하고, 한 번의 요청으로 여러 리소스를 가져올 수 있는 장점이 있습니다. 하지만 현재 Atlassian 에서는 Rest API 만큼 많은 기능을 GraphQL으로 지원하지 않습니다. 개발하기 전 필요한 GraphQL이 존재하는지 확인하는 작업이 필수적으로 필요합니다.
Webhook은 서버 간의 비동기 통신을 가능하게 해줍니다. Atlassian에서는 특정 이벤트(예: 이슈 생성, 업데이트, 삭제)가 발생할 때 실시간으로 데이터를 전달하기 위해 Webhook을 사용할 수 있습니다.
위의 방법을 활용하는 것이 때때로는 Rest API를 활용하는 것보다 효율적일 수 있습니다. 각 상황에 맞게 사용하시면 됩니다.

Atlassian GraphQL API >> https://developer.atlassian.com/platform/atlassian-graphql-api/graphql/#overview
Atlassian webhooks >> https://developer.atlassian.com/cloud/confluence/modules/webhook/


마치면서

지금까지 Atlassian Rest API를 효율적으로 사용하는 방법에 대해서 알아보았습니다.
해당 글은 Atlassina에서 제공하는 개발 문서와 Atlassian Developer Community의 글을 번역하고 재구성하여 쓴 글입니다. 출처는 하단을 참고해주세요.
위의 언급한 내용들은 Atlassian Rest API에만 국한되지 않는 내용이지만, 해당 글을 통해 Atlassian Rest API에서 어떤 기능을 지원하는지 알아가시면 좋을 거 같습니다.

Atlassian Rest API Development 관련 사이트
Atlassian Developer 공식 사이트 >> https://developer.atlassian.com
Jira Rest API 공식 문서 >> https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/#about
Confluence Rest API 공식 문서 >> https://developer.atlassian.com/cloud/confluence/rest/v2/
Bitbucket Rest API 공식 문서 >> https://developer.atlassian.com/cloud/bitbucket/rest/intro/#authentication
Atlassian Cloud Admin API 공식 문서 >> https://developer.atlassian.com/cloud/admin/rest-apis/


참고 링크
Atlassian 공식 문서 >> https://developer.atlassian.com/cloud/jira/platform/rate-limiting/
Efficient Use of Atlassian Cloud REST APIs 발표자료 >> https://www.atlassian.com/atlascamp/watch-sessions/2019/advanced-app-development/efficient-use-of-atlassian-cloud-rest-apis
이미지 출처 >> https://developer.atlassian.com/server/jira/platform/rest/v10000/intro/#gettingstarted

Leave a Reply

Your email address will not be published. Required fields are marked *