# NEO4J-BUG-001: Fixing Plan — Cartesian Product pada Query MERGE Edge

**Bug ID:** NEO4J-BUG-001  
**Severity:** 🟠 HIGH — Performance Degradation (O(n²) scaling)  
**Ditemukan:** Dari run faktual `ingestion.py` — 12 Mei 2026  
**File Target:** `app/services/ingestion.py`  
**Fungsi Target:** `create_lexicographical_edges_batch` (line ~833)  
**Status:** PLANNING  

---

## 1. Root Cause

Query Cypher untuk MERGE edge menggunakan pola **disconnected MATCH** yang memaksa Neo4j membuat cartesian product dari semua node `Literature`:

```cypher
-- QUERY BERMASALAH (line ~868)
UNWIND $batch AS edge
MATCH (a:Literature), (b:Literature)
WHERE elementId(a) = edge.node_min AND elementId(b) = edge.node_max
MERGE (a)-[r:SIMILAR_METHODOLOGY]->(b)
```

Neo4j harus load **semua node Literature** ke memori dan membuat **semua kombinasi pasangan** sebelum bisa filter dengan `WHERE`. Kompleksitas: **O(n²)** di mana n = jumlah node Literature.

**Bukti dari log run:**
```
GqlStatusObject gql_status='03N90'
"The disconnected pattern '(a:Literature), (b:Literature)' builds a cartesian product.
A cartesian product may produce a large amount of data and slow down query processing."
```

Warning ini muncul **5 kali** dalam satu run (sekali per rel_type: BACKGROUND, METHODOLOGY, GAP, OBJECTIVE, FUTUREWORK).

---

## 2. Proyeksi Dampak

| Jumlah Jurnal | Kombinasi Diperiksa | Estimasi Waktu |
|--------------|---------------------|----------------|
| 10 (sekarang) | 100 | < 1ms (tidak terasa) |
| 50 | 2.500 | ~10-50ms |
| 100 | 10.000 | ~100-500ms per query |
| 500 | 250.000 | ~5-30 detik per query |
| 1.000 | 1.000.000 | Kemungkinan timeout |

Query ini dipanggil **5x per run** (satu per rel_type). Pada 500 jurnal: `5 × 30s = 150 detik` hanya untuk edge generation.

---

## 3. Fix: Pisahkan MATCH menjadi Dua Lookup Terpisah

### Before (O(n²) — Cartesian Product)
```cypher
UNWIND $batch AS edge
MATCH (a:Literature), (b:Literature)
WHERE elementId(a) = edge.node_min AND elementId(b) = edge.node_max
MERGE (a)-[r:{rel_type}]->(b)
ON CREATE SET ...
ON MATCH SET ...
RETURN elementId(r) as rel_id
```

### After (O(1) — Direct Lookup)
```cypher
UNWIND $batch AS edge
MATCH (a:Literature) WHERE elementId(a) = edge.node_min
MATCH (b:Literature) WHERE elementId(b) = edge.node_max
MERGE (a)-[r:{rel_type}]->(b)
ON CREATE SET ...
ON MATCH SET ...
RETURN elementId(r) as rel_id
```

**Perbedaan:** Dua `MATCH` terpisah dengan kondisi langsung di masing-masing → Neo4j lookup by `elementId` (indexed) tanpa scan semua node. Kompleksitas turun dari **O(n²) → O(1)** per edge.

---

## 4. Langkah Implementasi

| # | Langkah | Detail |
|---|---------|--------|
| 1 | Ubah query di `create_lexicographical_edges_batch` | Pisah `MATCH (a), (b)` menjadi dua baris terpisah |
| 2 | Verify syntax `py_compile` | Tidak ada syntax error |
| 3 | Verify tidak ada warning cartesian product | Jalankan query kecil dan cek log Neo4j |

---

## 5. Acceptance Criteria

- [ ] Query tidak lagi menggunakan pola `MATCH (a:Literature), (b:Literature)` dalam satu baris
- [ ] Tidak ada warning `03N90` di log Neo4j setelah fix
- [ ] `python3 -m py_compile ingestion.py` → PASS
- [ ] Run pipeline dengan 10 PDF → semua edge terbentuk normal

---

## 6. Estimasi Waktu

| Task | Waktu |
|------|-------|
| Ubah query (1 baris → 2 baris) | 2 menit |
| Verify syntax | 1 menit |
| Run & verify tidak ada warning | 5 menit |
| **Total** | **~8 menit** |
