4141from collections import Counter , defaultdict
4242from 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
4646from .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
562555async 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