Authors: Szymon Wąs, Valiantsin Susha
- Database: PostgreSQL
- Chosen for its robustness, ACID compliance, and excellent support for complex queries and large datasets.
- Programming Language: Python 3
- Utilized for scripting due to its readability, extensive library ecosystem, and strong community support for data processing and database interactions.
- Libraries:
psycopg2: PostgreSQL adapter for Python, enabling efficient database connectivity and operations, includingexecute_valuesfor bulk inserts.argparse: For command-line argument parsing, facilitating flexible execution of scripts.tqdm: Provides a progress bar for the import process, enhancing user experience for large file imports.collections.deque: Used indbcli.pyfor efficient queue operations in Breadth-First Search (BFS) pathfinding.
The system consists of two primary components interacting with a PostgreSQL database:
- Database Schema (
schema.sql): Defines the structure of the knowledge graph withnodesandedgestables, including primary keys, foreign keys, and indexes for efficient data retrieval. - Data Importer (
import_data.py): Reads the.tsvfile, processes lines into node and edge data, and efficiently inserts them into the PostgreSQL database using batchedINSERToperations. It temporarily disables and re-enables indexes and constraints during import for performance. - CLI Query Tool (
dbcli.py): Provides a command-line interface for users to perform various queries on the imported knowledge graph data, ranging from basic node/edge lookups to complex pathfinding and similarity analyses.
flowchart LR
A["TSV Data File"] --> B["import_data.py(Data Importer)"]
B --> C["PostgreSQL Database(nodes, edges tables)"]
D["User Input"] --> E["dbcli.py(CLI Query Tool)"]
E --> C
- PostgreSQL Database Server: Must be installed and running (e.g., on
localhost:5432).- A database named "Projects2025" with a user "postgres" and password "postgres" is assumed for connections.
- Python 3: Installed on the system.
- Python Packages:
psycopg2:pip install psycopg2-binarytqdm:pip install tqdm
- CSKG TSV file: The CommonSense Knowledge Graph data file (in our case
cskg.tsv).
- Install PostgreSQL: Follow the official PostgreSQL documentation for your operating system.
- Create Database and User: Ensure a database named
Projects2025exists and is accessible by thepostgresuser with passwordpostgres.
CREATE DATABASE "Projects2025";
-- If 'postgres' user doesn't exist or needs password:
-- ALTER USER postgres WITH PASSWORD 'postgres';
- Install Python: If not already installed, download and install Python.
- Install Python Libraries:
pip install psycopg2-binary tqdm
- Place Files: Save
import_data.py,schema.sql, anddbcli.pyin your project directory. - Download TSV File: Obtain the
cskg.tsvfile and place it in an accessible location. (e.g.C:\Users\JustinBieber\pythonDatabase\cskg.tsv). - Initialize Database Schema: Run the
schema.sqlscript to create the necessary tables and indexes:
psql -h localhost -U postgres -d Projects2025 -f schema.sql
The import process is designed for efficiency with large datasets:
- Schema Definition: The
schema.sqldefinesnodes(node_id, node_label) andedges(edge_id, node1_id, node2_id, relation, relation_label) tables with appropriate primary and foreign keys. Indexes are created for performance onnode_id,node1_id, andnode2_id. - Connection Setup:
import_data.pyestablishes a connection to PostgreSQL with a 10-second timeout. - Optional Data Cleaning: If the
--cleanflag is provided, existingedgesandnodestables are truncated. - Performance Optimization: Before import, foreign key constraints and specific indexes are temporarily dropped to speed up batch inserts.
- TSV Parsing: The script reads the
.tsvfile line by line, skipping the header. - Data Extraction & Caching: Each line is parsed to extract
node1_id,node1_label,node2_id,node2_label,relation, andrelation_label. Nodes are added to anodes_cacheset to prevent duplicatenode_identries in thenodesbatch. - Batch Processing: Nodes and edges are collected into
nodes_batchandedges_batchlists. When theedges_batchreaches a definedbatch_size(default 50,000),execute_valuesis used for efficient bulk insertion into the database.nodesare inserted usingON CONFLICT (node_id) DO UPDATE SET node_label = EXCLUDED.node_label WHERE LENGTH(EXCLUDED.node_label) < LENGTH(nodes.node_label)to handle potential label updates if a shorter label is encountered.edgesare inserted usingON CONFLICT (edge_id) DO NOTHINGto prevent duplicate edge entries based onedge_id.
- Progress Tracking:
tqdmprovides a visual progress bar during the import. - Error Handling: Includes
try-exceptblocks for database errors and line-specific parsing errors, with transaction rollback on critical failures. - Index and Constraint Restoration: After the import, the dropped indexes and foreign key constraints are re-created to ensure data integrity and optimize query performance.
The dbcli.py script provides 18 different query operations on the imported CSKG data. Below are details for some key goals:
- Goal 1-8 (Successors, Predecessors, Neighbors, 2-hop connections): These queries use
JOINoperations betweenedgesandnodestables to retrieve connected nodes and their labels.STRING_AGGis used to combine multiple relations/labels into a single string for better readability in some outputs. For example, Query 1 finds successors usingWHERE e.node1_id = %s. - Goal 9-11 (Node Counts - Total, Sources, Sinks): Straightforward
SELECT COUNT(*)queries on thenodestable, withNOT EXISTSsubqueries for identifying source and sink nodes. - Goal 12 (Most Connected Node(s)): This query calculates the total degree (incoming + outgoing edges) for each node and then identifies nodes with the maximum degree. It performs separate counts for
node1_idandnode2_idin theedgestable and sums them up. - Goal 13 (Find all predecessors of a given node): This query identifies all nodes that point to the target node (predecessors) by joining the edges table with nodes table where node2_id matches the target. It aggregates results by node_label, combining multiple relations between the same nodes into comma-separated lists using STRING_AGG for cleaner output. The query preserves all connection types while grouping by predecessor labels. The relatively long execution time is expected given the need to process all edges in the graph
- Goal 14 (Rename a Node): This is a transaction involving an
INSERTof the new node,UPDATEstatements onnode1_idandnode2_idin theedgestable to reflect the new ID, and finally aDELETEof the old node. This ensures atomicity and data consistency during a rename. - Goal 15 (Similar Nodes - Common Parent/Child): Uses
WITHclauses andUNION ALLto find nodes that share a commonnode1_id(parent) ornode2_id(child) with the target node, provided they also share the samerelation. - Goal 16 (Shortest Path - BFS Implementation): Implemented directly in Python using a Breadth-First Search (BFS) algorithm. It dynamically queries neighbors from the database using
UNIONfor bothnode1_idandnode2_idfrom theedgestable, considering a set ofimportant_relationsand limiting depth. This approach allows for finding the shortest path efficiently in the potentially vast graph. - Goal 17 & 18 (Distant Synonyms/Antonyms - Recursive CTE): These queries utilize a
RECURSIVE CTE(synonym_paths) to traverse the graph based on/r/Synonymand/r/Antonymrelations. Thesigncolumn tracks whether the cumulative path indicates a synonym (positive sign) or antonym (negative sign). The query then filters for the specifieddistanceandsign(1 for synonym, -1 for antonym).ROW_NUMBER()is used to select the shortest path among multiple paths to the same node, ensuring unique and most direct results.
- Szymon Wąs:
- Designed and implemented the PostgreSQL database schema (
schema.sql), including table structures, primary/foreign keys, and indexing strategies. - Developed the core data import script (
import_data.py), focusing on efficient batch processing, temporary index/constraint management for performance, and error handling during large file ingestion. - Responsible for overall project setup, database connectivity, and ensuring data integrity during the import process.
- Also help develop queries 15-18.
- Designed and implemented the PostgreSQL database schema (
- Valiantsin Susha:
- Developed the comprehensive command-line interface (
dbcli.py), implementing all 18 specified query operations. - Focused on the implementation of advanced graph traversal algorithms within the CLI, such as the Breadth-First Search (BFS) for shortest path and the recursive CTEs for distant synonym/antonym analysis.
- Responsible for user interaction, query optimization, and the presentation of results from database operations.
- Developed the comprehensive command-line interface (
-
Data Import (
import_data.py):-
Input:
cskg.tsv. -
Outcome: Successful import of millions of nodes and edges into the PostgreSQL database.
-
Timings:
-
For a given TSV file, import time can range from 3 to 5 minutes depending on hardware and specific data characteristics.
-
Example output after a run:
Clearing existing data... Optimizing table structure... Starting import from file: C:\Users\szymo\pythonDatabase\cskg.tsv Importing data: 100%|██████████████████████████████████████████████████████| [total_lines]/[total_lines] [00:0X<00:00, X.XXlines/s] Restoring indexes and constraints... Import summary: Imported nodes: [Total Nodes Count, e.g., 500000] Imported edges: [Total Edges Count, e.g., 3000000] Skipped lines: [Count, e.g., 0-5] Total execution time: [X.XX]s (e.g., 180.50s)
-
-
Efficiency: The use of
psycopg2.extras.execute_valuesfor batched inserts and the temporary dropping of indexes/constraints significantly improve import speed compared to single row inserts. The progress bar provides real-time feedback.
-
-
Query Tool (
dbcli.py):- Example Run 1 (Find successors of a node):
Outcome: Returns nodes that
python dbcli.py 1 --node_id "/c/en/apple"applepoints to, along with their labels and the relations.Node: /c/en/fruit | Label: fruit | Relation: /r/IsA Node: /c/en/macintosh | Label: Macintosh | Relation: /r/HasA ...
- Example Run 2 (Shortest path between two nodes):
Outcome: Displays the shortest path found using BFS, including the distance and the nodes in the path with their labels.
python dbcli.py 16 --node_id "/c/en/dog" --node2_id "/c/en/leash"Running BFS from /c/en/dog to /c/en/leash... Shortest path distance: 1 Path nodes: - /c/en/dog (dog) - /c/en/leash (leash)
- Example Run 1 (Find successors of a node):
1. Data Import: To import the CSKG data into the database:
- Command:
python import_data.py --tsv "path/to/your/cskg.tsv" [--clean] [--batch <batch_size>]
-
Arguments:
--tsv <path>: Required. Specifies the full path to yourcskg.tsvfile (e.g.,C:\Users\szymo\pythonDatabase\cskg.tsv).--clean: Optional. If present, existing data in theedgesandnodestables will be truncated before import. Use with caution as it deletes all current data.--batch <size>: Optional. Sets the number of edges to process per batch insert (default: 50000). Adjust for performance based on system resources.
-
Querying the Database (CLI Tool): To query the imported data:
- Command:
python dbcli.py <goal_number> [--node_id <node_id>] [--new_id <new_node_id>] [--new_label <new_node_label>] [--node2_id <second_node_id>] [--distance <int_distance>]-
Arguments:
<goal_number>: Required. An integer from 1 to 18 representing the desired operation.--node_id <node_id>: Required for operations 1-8, 14-18. The primary node ID for the query (e.g.,/c/en/dog).--new_id <new_node_id>: Required for operation 14. The new ID for renaming a node.--new_label <new_node_label>: Required for operation 14. The new label for renaming a node.--node2_id <second_node_id>: Required for operation 16. The second node ID for shortest path queries.--distance <int_distance>: Required for operations 17-18. The distance for distant synonym/antonym queries.
10. Self-evaluation: efficiency should be discussed, strategies for future mitigation of identified shortcomings.
-
Efficiency Achievements:
- Bulk Import: The use of
psycopg2.extras.execute_valuesis highly efficient for inserting large volumes of data, minimizing database round trips. - Index Management during Import: Temporarily dropping and re-creating indexes and constraints significantly reduces the overhead during large-scale data insertion, making the import process much faster.
- Node Caching: Maintaining a
nodes_cacheset during import prevents redundant attempts to insert the same node, reducing database calls andON CONFLICToverhead for nodes. - Optimized Queries: The
dbcli.pyleverages PostgreSQL's capabilities with efficient SQL queries, includingJOINs,UNION,WITHclauses (CTEs), andGROUP BYwithSTRING_AGGfor data aggregation. The recursive CTEs for synonym/antonym queries are particularly powerful for graph traversal within the database. - In-Memory BFS for Shortest Path: While a purely SQL-based BFS can be complex and resource-intensive for large depths, the Python-based BFS in
dbcli.pyallows for controlled traversal and resource management.
- Bulk Import: The use of
-
Identified Shortcomings & Future Mitigation:
- Memory Usage during Import (Nodes Cache): For extremely large datasets with a vast number of unique nodes, the
nodes_cache(a Python set) could potentially consume a significant amount of memory.- Mitigation: For even larger files (e.g., terabytes), consider alternative strategies:
- Import nodes and edges in separate passes: first all unique nodes, then all edges. This eliminates the need for the
nodes_cacheduring edge import. - Use a temporary staging table in the database for initial import, then de-duplicate and move to final tables.
- Implement a more sophisticated node-ID management system that queries the database for existence in batches, rather than holding all IDs in memory.
- Import nodes and edges in separate passes: first all unique nodes, then all edges. This eliminates the need for the
- Mitigation: For even larger files (e.g., terabytes), consider alternative strategies:
- Scalability of Python-based BFS: While effective for moderate path lengths and a limited set of "important" relations, a Python-based BFS could become a bottleneck for finding paths in extremely dense graphs or for very large distances, as it involves many individual database queries.
- Mitigation: For more advanced graph analysis, consider:
- Using a dedicated graph database (e.g., Neo4j) if graph traversal performance is paramount.
- Leveraging PostgreSQL extensions like
pgRoutingor implementing more complex SQL recursive CTEs that are fully optimized for graph traversals within the database engine itself for specific pathfinding scenarios. - For general graph analysis, exporting the graph to a specialized graph processing library (e.g., NetworkX in Python) for in-memory analysis might be more efficient for certain tasks.
- Mitigation: For more advanced graph analysis, consider:
- Hardcoded Database Credentials: The database connection details (dbname, user, password, host, port) are hardcoded in both
import_data.pyanddbcli.py.- Mitigation: Externalize these configurations using:
- Environment variables.
- A separate configuration file (e.g.,
config.inior.envfile) that is not committed to version control. - PostgreSQL's
pgpass.conffile for password management.
- Mitigation: Externalize these configurations using:
- Limited Error Reporting in CLI: While general database errors are caught, specific error messages for invalid node IDs or parameters in
dbcli.pycould be more user-friendly.- Mitigation: Add more specific validation checks and clearer error messages for user input within
dbcli.py.
- Mitigation: Add more specific validation checks and clearer error messages for user input within
- Performance of
ON CONFLICTfor Labels: TheON CONFLICTclause onnodeschecksLENGTH(EXCLUDED.node_label) < LENGTH(nodes.node_label). While functional, this might introduce minor overhead compared to a simplerDO NOTHINGif label updates are not a primary concern or are handled externally.- Mitigation: Evaluate if the label update logic is strictly necessary. If not, simplifying to
ON CONFLICT (node_id) DO NOTHINGorDO UPDATE SET node_label = EXCLUDED.node_labelmight offer a slight performance gain.
- Mitigation: Evaluate if the label update logic is strictly necessary. If not, simplifying to
- Memory Usage during Import (Nodes Cache): For extremely large datasets with a vast number of unique nodes, the