Skip to content

Commit 1e9dee1

Browse files
authored
Feature/fix lock error (#969)
* feat: refine query context * feat: refine query context * feat: speed up graph index
1 parent bb2227e commit 1e9dee1

2 files changed

Lines changed: 97 additions & 94 deletions

File tree

aperag/graph/lightrag/lightrag.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -501,7 +501,7 @@ async def _grouping_process_chunk_results(
501501
)
502502

503503
# Process components concurrently with semaphore
504-
semaphore = asyncio.Semaphore(self.llm_model_max_async)
504+
semaphore = asyncio.Semaphore(1)
505505

506506
async def _process_component_with_semaphore(task_data):
507507
async with semaphore:
@@ -761,12 +761,7 @@ async def aprocess_graph_indexing(
761761
lightrag_logger.error(f"Graph indexing failed: {str(e)}")
762762
else:
763763
logger.error(f"Graph indexing failed: {str(e)}", exc_info=True)
764-
return {
765-
"status": "error",
766-
"error": str(e),
767-
"chunks_processed": 0,
768-
"collection_id": collection_id,
769-
}
764+
raise e
770765

771766
# ============= End of New Stateless Interfaces =============
772767

aperag/graph/lightrag/operate.py

Lines changed: 95 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
from collections import Counter, defaultdict
4242
from typing import Any, AsyncIterator
4343

44-
from aperag.concurrent_control import MultiLock, get_or_create_lock
44+
from aperag.concurrent_control import get_or_create_lock
4545

4646
from .base import (
4747
BaseGraphStorage,
@@ -535,32 +535,26 @@ async def merge_nodes_and_edges(
535535
Returns:
536536
Dict with entity_count and relation_count
537537
"""
538-
539-
# Create locks for all entities in this component
540-
entity_locks = []
541-
for entity_name in sorted(component): # Sort to prevent deadlock
542-
lock = get_or_create_lock(f"entity:{entity_name}:{workspace}")
543-
entity_locks.append(lock)
544-
545-
# Use MultiLock to acquire all locks for this component
546-
async with MultiLock(entity_locks):
547-
return await _merge_nodes_and_edges_impl(
548-
chunk_results,
549-
knowledge_graph_inst,
550-
entity_vdb,
551-
relationships_vdb,
552-
llm_model_func,
553-
tokenizer,
554-
llm_model_max_token_size,
555-
summary_to_max_tokens,
556-
addon_params,
557-
force_llm_summary_on_merge,
558-
lightrag_logger,
559-
)
538+
# Now using fine-grained locking inside _merge_nodes_and_edges_impl
539+
return await _merge_nodes_and_edges_impl(
540+
chunk_results,
541+
workspace,
542+
knowledge_graph_inst,
543+
entity_vdb,
544+
relationships_vdb,
545+
llm_model_func,
546+
tokenizer,
547+
llm_model_max_token_size,
548+
summary_to_max_tokens,
549+
addon_params,
550+
force_llm_summary_on_merge,
551+
lightrag_logger,
552+
)
560553

561554

562555
async def _merge_nodes_and_edges_impl(
563556
chunk_results: list,
557+
workspace: str,
564558
knowledge_graph_inst: BaseGraphStorage,
565559
entity_vdb: BaseVectorStorage,
566560
relationships_vdb: BaseVectorStorage,
@@ -572,7 +566,10 @@ async def _merge_nodes_and_edges_impl(
572566
force_llm_summary_on_merge,
573567
lightrag_logger: LightRAGLogger | None = None,
574568
) -> dict[str, int]:
575-
"""Internal implementation of merge_nodes_and_edges"""
569+
"""Internal implementation of merge_nodes_and_edges with fine-grained locking"""
570+
571+
# Extract language from addon_params
572+
language = addon_params.get("language", "English")
576573

577574
# Collect all nodes and edges from all chunks
578575
all_nodes = defaultdict(list)
@@ -588,74 +585,85 @@ async def _merge_nodes_and_edges_impl(
588585
sorted_edge_key = tuple(sorted(edge_key))
589586
all_edges[sorted_edge_key].extend(edges)
590587

591-
# Centralized processing of all nodes and edges
592-
entities_data = []
593-
relationships_data = []
588+
# Process entities with fine-grained locking
589+
entity_count = 0
594590

595-
# Process and update all entities at once
596591
for entity_name, entities in all_nodes.items():
597-
entity_data = await _merge_nodes_then_upsert(
598-
entity_name,
599-
entities,
600-
knowledge_graph_inst,
601-
llm_model_func,
602-
tokenizer,
603-
llm_model_max_token_size,
604-
summary_to_max_tokens,
605-
addon_params,
606-
force_llm_summary_on_merge,
607-
lightrag_logger,
608-
)
609-
entities_data.append(entity_data)
592+
# Create lock for this specific entity
593+
entity_lock = get_or_create_lock(f"entity:{entity_name}:{workspace}")
594+
595+
async with entity_lock:
596+
# Process and update entity in graph db
597+
entity_data = await _merge_nodes_then_upsert(
598+
entity_name,
599+
entities,
600+
knowledge_graph_inst,
601+
llm_model_func,
602+
tokenizer,
603+
llm_model_max_token_size,
604+
summary_to_max_tokens,
605+
language, # Pass language instead of addon_params
606+
force_llm_summary_on_merge,
607+
lightrag_logger,
608+
)
609+
610+
# Update entity in vector db immediately under the same lock
611+
if entity_vdb is not None and entity_data:
612+
vdb_data = {
613+
compute_mdhash_id(entity_data["entity_name"], prefix="ent-"): {
614+
"entity_name": entity_data["entity_name"],
615+
"entity_type": entity_data["entity_type"],
616+
"content": f"{entity_data['entity_name']}\n{entity_data['description']}",
617+
"source_id": entity_data["source_id"],
618+
"file_path": entity_data.get("file_path", "unknown_source"),
619+
}
620+
}
621+
await entity_vdb.upsert(vdb_data)
622+
623+
entity_count += 1
624+
625+
# Process relationships with fine-grained locking
626+
relation_count = 0
610627

611-
# Process and update all relationships at once
612628
for edge_key, edges in all_edges.items():
613-
edge_data = await _merge_edges_then_upsert(
614-
edge_key[0],
615-
edge_key[1],
616-
edges,
617-
knowledge_graph_inst,
618-
llm_model_func,
619-
tokenizer,
620-
llm_model_max_token_size,
621-
summary_to_max_tokens,
622-
addon_params,
623-
force_llm_summary_on_merge,
624-
lightrag_logger,
625-
)
626-
if edge_data is not None:
627-
relationships_data.append(edge_data)
628-
629-
# Update vector databases with all collected data
630-
if entity_vdb is not None and entities_data:
631-
data_for_vdb = {
632-
compute_mdhash_id(dp["entity_name"], prefix="ent-"): {
633-
"entity_name": dp["entity_name"],
634-
"entity_type": dp["entity_type"],
635-
"content": f"{dp['entity_name']}\n{dp['description']}",
636-
"source_id": dp["source_id"],
637-
"file_path": dp.get("file_path", "unknown_source"),
638-
}
639-
for dp in entities_data
640-
}
641-
await entity_vdb.upsert(data_for_vdb)
642-
643-
if relationships_vdb is not None and relationships_data:
644-
data_for_vdb = {
645-
compute_mdhash_id(dp["src_id"] + dp["tgt_id"], prefix="rel-"): {
646-
"src_id": dp["src_id"],
647-
"tgt_id": dp["tgt_id"],
648-
"keywords": dp["keywords"],
649-
"content": f"{dp['src_id']}\t{dp['tgt_id']}\n{dp['keywords']}\n{dp['description']}",
650-
"source_id": dp["source_id"],
651-
"file_path": dp.get("file_path", "unknown_source"),
652-
}
653-
for dp in relationships_data
654-
}
655-
await relationships_vdb.upsert(data_for_vdb)
629+
# Create lock for this specific relationship
630+
# Sort edge key to ensure consistent lock naming
631+
sorted_edge_key = tuple(sorted(edge_key))
632+
relationship_lock = get_or_create_lock(f"relationship:{sorted_edge_key[0]}:{sorted_edge_key[1]}:{workspace}")
633+
634+
async with relationship_lock:
635+
# Process and update relationship in graph db
636+
edge_data = await _merge_edges_then_upsert(
637+
edge_key[0],
638+
edge_key[1],
639+
edges,
640+
knowledge_graph_inst,
641+
llm_model_func,
642+
tokenizer,
643+
llm_model_max_token_size,
644+
summary_to_max_tokens,
645+
language, # Pass language instead of addon_params
646+
force_llm_summary_on_merge,
647+
lightrag_logger,
648+
)
649+
650+
# Update relationship in vector db immediately under the same lock
651+
if relationships_vdb is not None and edge_data is not None:
652+
vdb_data = {
653+
compute_mdhash_id(edge_data["src_id"] + edge_data["tgt_id"], prefix="rel-"): {
654+
"src_id": edge_data["src_id"],
655+
"tgt_id": edge_data["tgt_id"],
656+
"keywords": edge_data["keywords"],
657+
"content": f"{edge_data['src_id']}\t{edge_data['tgt_id']}\n{edge_data['keywords']}\n{edge_data['description']}",
658+
"source_id": edge_data["source_id"],
659+
"file_path": edge_data.get("file_path", "unknown_source"),
660+
}
661+
}
662+
await relationships_vdb.upsert(vdb_data)
663+
664+
if edge_data is not None:
665+
relation_count += 1
656666

657-
entity_count = len(entities_data)
658-
relation_count = len(relationships_data)
659667
return {"entity_count": entity_count, "relation_count": relation_count}
660668

661669

0 commit comments

Comments
 (0)