🗄️ 데이터베이스

ArangoDB

Multi-Model Database

문서(Document), 그래프(Graph), 키-값(Key-Value) 모델을 단일 엔진에서 지원하는 오픈소스 멀티모델 데이터베이스입니다. AQL(ArangoDB Query Language)로 모든 데이터 모델을 통합 쿼리할 수 있습니다.

📖 상세 설명

ArangoDB는 2011년 독일에서 시작된 네이티브 멀티모델 데이터베이스입니다. 하나의 엔진으로 문서 저장소(MongoDB 대체), 그래프 데이터베이스(Neo4j 대체), 키-값 저장소를 모두 처리합니다. 여러 데이터베이스를 운영할 필요 없이 하나의 시스템에서 다양한 데이터 구조를 관리할 수 있습니다.

AQL(ArangoDB Query Language)은 SQL과 유사한 선언적 쿼리 언어로, 문서 필터링, 그래프 순회(Graph Traversal), 조인을 단일 쿼리로 처리합니다. FOR...FILTER...RETURN 패턴으로 직관적이며, 그래프 탐색에 특화된 GRAPH 키워드를 제공합니다.

Foxx Microservices는 ArangoDB 내부에서 JavaScript로 마이크로서비스를 직접 실행하는 기능입니다. 별도 애플리케이션 서버 없이 데이터베이스와 비즈니스 로직을 통합할 수 있어 레이턴시를 줄이고 아키텍처를 단순화합니다.

클러스터링SmartGraphs를 통해 수평 확장과 분산 그래프 처리를 지원합니다. 데이터를 자동으로 샤딩하고, 그래프 순회 시 네트워크 홉을 최소화하는 스마트 샤딩으로 대규모 그래프 쿼리 성능을 보장합니다.

추천 시스템, 사기 탐지, 지식 그래프, IoT 데이터 처리 등 다양한 데이터 모델이 혼합된 유스케이스에 적합합니다.

💻 코드 예제

# ArangoDB Python 드라이버 (python-arango)
from arango import ArangoClient

# 클라이언트 연결
client = ArangoClient(hosts='http://localhost:8529')

# 시스템 DB 연결 후 사용자 DB 생성
sys_db = client.db('_system', username='root', password='password')

# 새 데이터베이스 생성 (없으면)
if not sys_db.has_database('myapp'):
    sys_db.create_database('myapp')

# 애플리케이션 DB 연결
db = client.db('myapp', username='root', password='password')

# 컬렉션 생성 (문서 저장소)
if not db.has_collection('users'):
    users = db.create_collection('users')
else:
    users = db.collection('users')

# 엣지 컬렉션 생성 (그래프 관계)
if not db.has_collection('follows'):
    follows = db.create_collection('follows', edge=True)
else:
    follows = db.collection('follows')

# 문서 삽입
user1 = users.insert({'_key': 'alice', 'name': 'Alice', 'age': 28})
user2 = users.insert({'_key': 'bob', 'name': 'Bob', 'age': 32})
user3 = users.insert({'_key': 'charlie', 'name': 'Charlie', 'age': 25})

# 엣지 삽입 (관계 생성)
follows.insert({'_from': 'users/alice', '_to': 'users/bob'})
follows.insert({'_from': 'users/bob', '_to': 'users/charlie'})
follows.insert({'_from': 'users/alice', '_to': 'users/charlie'})

# 그래프 생성
if not db.has_graph('social'):
    graph = db.create_graph('social')
    graph.create_edge_definition(
        edge_collection='follows',
        from_vertex_collections=['users'],
        to_vertex_collections=['users']
    )
else:
    graph = db.graph('social')

# AQL 쿼리 실행 - 2단계 팔로우 관계 탐색
cursor = db.aql.execute('''
    FOR user IN users
        FILTER user._key == 'alice'
        FOR friend, edge, path IN 1..2 OUTBOUND user follows
            RETURN DISTINCT {
                name: friend.name,
                depth: LENGTH(path.edges)
            }
''')

print("Alice의 팔로우 네트워크:")
for doc in cursor:
    print(f"  {doc['name']} (깊이: {doc['depth']})")

# 문서 + 그래프 조합 쿼리
cursor = db.aql.execute('''
    FOR user IN users
        FILTER user.age >= 25
        LET follower_count = LENGTH(
            FOR f IN follows FILTER f._to == user._id RETURN 1
        )
        RETURN {
            name: user.name,
            age: user.age,
            followers: follower_count
        }
''')

print("\n팔로워 통계:")
for doc in cursor:
    print(f"  {doc['name']}: {doc['followers']}명")
// ArangoDB Node.js 드라이버 (arangojs)
const { Database, aql } = require('arangojs');

async function main() {
    // 데이터베이스 연결
    const db = new Database({
        url: 'http://localhost:8529',
        databaseName: 'myapp',
        auth: { username: 'root', password: 'password' }
    });

    // 컬렉션 접근
    const products = db.collection('products');
    const categories = db.collection('categories');
    const belongs_to = db.collection('belongs_to'); // 엣지

    // 문서 삽입 (upsert 패턴)
    await db.query(aql`
        UPSERT { _key: "laptop-001" }
        INSERT {
            _key: "laptop-001",
            name: "MacBook Pro",
            price: 2499,
            specs: { cpu: "M3", ram: "16GB", storage: "512GB" }
        }
        UPDATE { price: 2499 }
        IN products
    `);

    // 중첩 문서 쿼리
    const cursor = await db.query(aql`
        FOR p IN products
            FILTER p.specs.ram == "16GB"
            SORT p.price DESC
            RETURN { name: p.name, price: p.price }
    `);

    console.log('16GB RAM 제품:');
    for await (const product of cursor) {
        console.log(`  ${product.name}: $${product.price}`);
    }

    // 그래프 순회 - 카테고리별 제품 탐색
    const categoryProducts = await db.query(aql`
        FOR cat IN categories
            FILTER cat.name == "Electronics"
            FOR product IN 1..1 INBOUND cat belongs_to
                COLLECT category = cat.name INTO products = product
                RETURN {
                    category: category,
                    products: products[*].name,
                    total: LENGTH(products)
                }
    `);

    console.log('\n카테고리별 제품:');
    for await (const result of categoryProducts) {
        console.log(`  ${result.category}: ${result.total}개`);
    }

    // 트랜잭션 처리
    const trx = await db.beginTransaction({
        read: ['products'],
        write: ['orders', 'products']
    });

    try {
        await trx.step(() => db.query(aql`
            INSERT {
                product_id: "laptop-001",
                quantity: 1,
                total: 2499,
                created_at: DATE_NOW()
            } INTO orders
        `));

        await trx.step(() => db.query(aql`
            UPDATE { _key: "laptop-001" }
            WITH { stock: OLD.stock - 1 }
            IN products
        `));

        await trx.commit();
        console.log('\n주문 완료!');
    } catch (err) {
        await trx.abort();
        console.error('트랜잭션 실패:', err.message);
    }
}

main().catch(console.error);
// ============================================
// AQL 기본 쿼리
// ============================================

// 문서 조회 (기본)
FOR user IN users
    FILTER user.age >= 25 AND user.status == "active"
    SORT user.created_at DESC
    LIMIT 10
    RETURN user

// 문서 삽입
INSERT {
    name: "신규 사용자",
    email: "new@example.com",
    created_at: DATE_NOW()
} INTO users
RETURN NEW

// 문서 업데이트
UPDATE "user123" WITH {
    last_login: DATE_NOW(),
    login_count: OLD.login_count + 1
} IN users
RETURN NEW


// ============================================
// 그래프 순회 (Graph Traversal)
// ============================================

// 1단계 팔로우 관계
FOR user IN users
    FILTER user._key == "alice"
    FOR friend IN 1..1 OUTBOUND user follows
        RETURN friend.name

// 최대 3단계까지 관계 탐색
FOR user IN users
    FILTER user._key == "alice"
    FOR connected, edge, path IN 1..3 ANY user follows
        OPTIONS { bfs: true, uniqueVertices: 'global' }
        RETURN {
            person: connected.name,
            depth: LENGTH(path.edges),
            via: path.vertices[*].name
        }

// 최단 경로 찾기
FOR v, e IN OUTBOUND SHORTEST_PATH
    'users/alice' TO 'users/charlie'
    GRAPH 'social'
    RETURN { vertex: v.name, edge: e }


// ============================================
// 조인과 집계
// ============================================

// 컬렉션 조인
FOR order IN orders
    FOR user IN users
        FILTER order.user_id == user._key
        RETURN {
            order_id: order._key,
            customer: user.name,
            total: order.amount
        }

// 그룹화 및 집계
FOR order IN orders
    COLLECT user_id = order.user_id
    AGGREGATE total = SUM(order.amount), count = LENGTH(1)
    SORT total DESC
    RETURN {
        user: user_id,
        order_count: count,
        total_spent: total
    }


// ============================================
// 전문 검색 (Full-Text Search)
// ============================================

// Analyzer 뷰 사용
FOR doc IN product_search
    SEARCH ANALYZER(
        doc.description IN TOKENS("wireless bluetooth headphones", "text_en"),
        "text_en"
    )
    SORT BM25(doc) DESC
    LIMIT 10
    RETURN { name: doc.name, score: BM25(doc) }

🗣️ 실무에서 이렇게 말하세요

💬 아키텍처 설계 회의에서
"추천 시스템에 사용자 프로필은 문서로, 관계는 그래프로 저장해야 합니다. MongoDB와 Neo4j 두 개 운영하기보다 ArangoDB 하나로 통합하면 운영 복잡도를 크게 줄일 수 있어요. 쿼리도 AQL 하나로 문서 필터링과 그래프 순회를 동시에 처리할 수 있습니다."
💬 기술 스택 선정 논의에서
"사기 탐지 시스템은 트랜잭션 패턴 분석이 핵심입니다. ArangoDB의 SmartGraphs를 쓰면 분산 환경에서도 그래프 순회 성능이 좋아요. 관련 노드를 같은 샤드에 배치해서 네트워크 홉을 최소화합니다."
💬 마이그레이션 계획 논의에서
"MongoDB에서 ArangoDB로 마이그레이션할 때 문서 구조는 거의 그대로 가져올 수 있습니다. arangoimport 도구로 JSON 데이터 임포트하고, 쿼리만 Aggregation Pipeline에서 AQL로 변환하면 됩니다. 그래프 관계는 추가로 엣지 컬렉션만 설계하면 되고요."

⚠️ 주의사항 & 베스트 프랙티스

커뮤니티 규모 한계

MongoDB, Neo4j에 비해 커뮤니티와 생태계가 작습니다. 특정 문제 해결 시 공식 문서와 GitHub 이슈에 의존해야 할 수 있습니다.

대규모 그래프 순회 메모리

복잡한 그래프 쿼리는 메모리를 많이 사용합니다. 깊은 순회(depth > 3)시 OPTIONS { bfs: true }와 LIMIT을 적절히 사용하세요.

Foxx 마이크로서비스 제약

Foxx는 편리하지만 V8 JavaScript 런타임 제약이 있습니다. CPU 집약적 작업이나 외부 라이브러리가 많이 필요하면 별도 서버를 고려하세요.

베스트 프랙티스

그래프 설계 시 엣지 컬렉션을 명확히 분리하고, SmartGraphs로 관련 데이터를 같은 샤드에 배치하세요. 인덱스 설계와 쿼리 EXPLAIN으로 성능을 최적화하세요.

🔗 관련 용어

📚 더 배우기