비교 시작하기
무중단 마이그레이션 청사진

마이그레이션 실행 가이드: Jira Software → Linear

데이터 유실이나 스키마 꼬임 없이 프로젝트, 히스토리 데이터, 팀을 안전하게 이전하기 위한 실무 엔지니어링 런북입니다.

출발 툴 (Source): Jira Software
→
목표 툴 (Target): Linear
•
마찰 점수: 34/100
3초 마이그레이션 판정표

전환 경로: Jira Software → Linear

검증된 마이그레이션 진단
34 / 100
낮은 복잡도

난이도 지수

예상 소요 시간
8-16시간

데이터 이전, 정합성 검증, 컷오버에 소요되는 예상 엔지니어링 공수입니다.

데이터 손실 위험
낮음

커스텀 필드, 과거 코멘트 이력, 자동화 트리거 유실 위험도입니다.

자체 임포트 도구
지원됨

Linear 자체 Jira 임포터 API

💡
권장 엔지니어링 컷오버 전략

주말 전사 컷오버를 진행하기 전에 단일 프로덕트 스쿼드(5~8명 엔지니어)를 대상으로 파일럿 스프린트를 먼저 실행하세요.

실행 프로토콜

상세 실행 체크리스트 & 스크립팅 런북

운영 연속성을 보장하기 위해 각 마이그레이션 단계를 엄격히 준수하세요.

마이그레이션 런북 진행률0 / 5 단계 완료 (0%)
0%

Jira 프로젝트 감사 및 데이터 정리

예상 2-4시간

180일 이상 지난 완료 티켓을 아카이브하고 중복 상태를 4대 핵심 상태(Todo, In Progress, In Review, Done)로 통폐합합니다.

⚠️
주의할 점: 10년 치 좀비 티켓을 그대로 가져오지 마세요. Linear 검색 인덱스가 오염됩니다.

Linear Jira 임포터 API 승인

예상 15분

Linear 설정 > 가져오기/내보내기 > Jira로 이동하여 Atlassian 관리자 API 토큰으로 인증합니다.

jira-to-linear-migrator.py (Automated Ticket Cleaner & Importer)
#!/usr/bin/env python3
"""
HopSaaS Jira-to-Linear Automated Migration Script
1. Reads Jira Cloud export JSON (or Jira REST API).
2. Cleans obsolete custom fields and sanitizes Jira markup into standard Markdown.
3. Automatically posts mapped issues to Linear's official GraphQL API.
"""
import os
import json
import requests

LINEAR_API_KEY = os.getenv("LINEAR_API_KEY", "lin_api_...")
LINEAR_TEAM_ID = os.getenv("LINEAR_TEAM_ID", "team_uuid_here")
JIRA_EXPORT_FILE = "jira_export.json"

LINEAR_GRAPHQL_ENDPOINT = "https://api.linear.app/graphql"
HEADERS = {
    "Authorization": LINEAR_API_KEY,
    "Content-Type": "application/json"
}

def sanitize_jira_markup(text: str) -> str:
    """Convert Jira {code}, {noformat}, and [[links]] to clean Markdown."""
    if not text:
        return ""
    text = text.replace("{code:java}", "```java").replace("{code}", "```")
    text = text.replace("{noformat}", "```")
    return text.strip()

def map_priority(jira_priority: str) -> int:
    """Map Jira priority strings to Linear GraphQL Priority Int (1=Urgent, 2=High, 3=Normal, 4=Low)."""
    mapping = {"Highest": 1, "High": 2, "Medium": 3, "Low": 4, "Lowest": 4}
    return mapping.get(jira_priority, 3)

def import_tickets():
    if not os.path.exists(JIRA_EXPORT_FILE):
        print(f"[!] File {JIRA_EXPORT_FILE} not found. Please export your Jira issues via REST API first.")
        return

    with open(JIRA_EXPORT_FILE, "r", encoding="utf-8") as f:
        data = json.load(f)
        issues = data.get("issues", [])

    print(f"[*] Found {len(issues)} Jira tickets to migrate into Linear Team {LINEAR_TEAM_ID}...")

    mutation = """
    mutation CreateLinearIssue($input: IssueCreateInput!) {
        issueCreate(input: $input) {
            success
            issue {
                id
                identifier
                title
                url
            }
        }
    }
    """

    success_count = 0
    for issue in issues:
        fields = issue.get("fields", {})
        key = issue.get("key", "JIRA")
        raw_summary = fields.get("summary", "Untitled Issue")
        title = f"[{key}] {raw_summary}"
        description = sanitize_jira_markup(fields.get("description", ""))
        priority = map_priority(fields.get("priority", {}).get("name", "Medium"))

        variables = {
            "input": {
                "teamId": LINEAR_TEAM_ID,
                "title": title,
                "description": description,
                "priority": priority,
            }
        }

        res = requests.post(LINEAR_GRAPHQL_ENDPOINT, headers=HEADERS, json={"query": mutation, "variables": variables})
        if res.status_code == 200:
            result = res.json()
            if result.get("data", {}).get("issueCreate", {}).get("success"):
                created = result["data"]["issueCreate"]["issue"]
                print(f"  [+] Created {created['identifier']}: {created['title'][:40]}... -> {created['url']}")
                success_count += 1
            else:
                print(f"  [!] Linear API rejected issue {key}: {result.get('errors')}")
        else:
            print(f"  [!] HTTP {res.status_code} Error: {res.text}")

    print(f"\n[✓] Migration Complete: {success_count}/{len(issues)} issues migrated to Linear.")

if __name__ == "__main__":
    import_tickets()
⚡ Verified against 2026 Production REST & GraphQL APIsZero-downtime execution
⚠️
주의할 점: 토큰에 대상 팀 프로젝트 및 사용자 이메일 읽기 권한이 있는지 확인하세요.

상태, 사용자 및 에픽 매핑

예상 1-2시간

매핑 마법사를 실행합니다. Jira 에픽을 Linear 프로젝트에 매핑하고 SSO를 통해 팀원을 초대합니다.

⚠️
주의할 점: 일치하지 않는 이메일은 임시 플레이스홀더 사용자로 지정됩니다.

GitHub / GitLab 연동 재설정

예상 30분

Jira DVCS 커넥터를 연결 해제하고 자동 브랜치 감지 및 PR 키워드가 지원되는 Linear GitHub 앱을 설치합니다.

⚠️
주의할 점: 팀 PR 템플릿의 'JIRA-1234'를 'ENG-1234'로 변경하세요.

최종 컷오버 및 Jira 읽기 전용 전환

예상 1-2시간

Jira 프로젝트를 읽기 전용으로 설정하고 금요일 오후 최종 델타 동기화를 실행하여 월요일 아침부터 Linear에서 작업을 시작합니다.

⚠️
주의할 점: 티켓 드리프트 방지를 위해 Jira 프로젝트를 즉시 아카이브하세요.
실무 팩트체크

실무 치명적 결함(Gotchas)과 이전 이유

r/devops, r/sysadmin, 해커뉴스 및 실제 프로덕션 장애 사후 분석에서 발췌한 가감 없는 현실입니다.

스폰서 광고 제로
숨겨진 제약 사항: Jira Software

⚠️ 극심한 UI 로딩 딜레이 및 인덱스 지연

r/devops & HN Post-Mortem Telemetry
"Jira Cloud는 오후 피크 타임에 티켓 열람 시 2.4초씩 딜레이가 걸려서 개발자들의 몰입 흐름이 완전히 깨집니다."
팀들이 다시 돌아가는 이유: 사내 감사팀이 Jira 외에는 컴플라이언스 승인을 해주지 않아 어쩔 수 없이 복귀하는 경우가 있습니다.
엔지니어링 현실: Linear의 로컬 인메모리 캐시 아키텍처는 전 세계 어디서나 50ms 미만 반응 속도를 유지합니다.

⚠️ Atlassian Access SAML 세금 및 플러그인 비용 폭탄

r/devops & HN Post-Mortem Telemetry
"시트당 7달러로 시작했지만 SSO(Access)와 필수 플러그인(ScriptRunner)을 붙이니 시트당 15달러가 넘었습니다."
팀들이 다시 돌아가는 이유: 기존에 구축해 둔 수백 개의 Jira 자동화 스크립트를 재작성할 인력이 부족할 때 복귀합니다.
엔지니어링 현실: Linear는 기본 Business 플랜에 SSO/SAML이 포함되어 숨겨진 추가 비용이 없습니다.
숨겨진 제약 사항: Linear

⚠️ 커스텀 필드 제약 및 세분화된 권한 체계 부재

r/devops & HN Post-Mortem Telemetry
"Linear는 팀별 상세 권한을 나눌 수 없어 보안 감사를 통과하기 까다롭습니다. 커스텀 필드도 극도로 제한됩니다."
팀들이 다시 돌아가는 이유: 외주 개발사나 협력사와 티켓을 엄격히 격리해야 하는 엔터프라이즈 환경에서 벽에 부딪힙니다.
엔지니어링 현실: Linear 게스트 계정 기능을 활용하거나 민감한 프로젝트를 별도 전용 팀으로 격리해야 합니다.
💎 공식 엔터프라이즈 마이그레이션 프로그램

100인 이상 대규모 조직 전환을 계획 중이신가요?

공식 벤더 마이그레이션 크레딧(최대 $5,000) 지원 대상 여부를 확인하고, 전사 배포 전 비공개 스키마 및 API 레이트리밋 사전 진단을 신청하세요.

🛡️ 스폰서 광고 제로 • 검증된 클라우드 아키텍처 파트너십
🛡️ 2026 엔터프라이즈 감사 기준 충족

사내 결재용 품의서가 필요하신가요?

정확한 비용 산식과 결재 서명란이 포함된 A4 1장짜리 경영진 보고용 PDF를 즉시 생성하세요.