Insert multiple comments in optimized batches using PostgreSQL executemany. Args: comments: List of comment dictionaries with Reddit JSON structure progress_callback: Optional callback function(processed, total) for progress updates auto_tune_batch_size:
(
self,
comments: list[dict[str, Any]],
progress_callback: Callable[[int, int], None] | None = None,
auto_tune_batch_size: bool = True,
initial_batch_size: int = 1000,
)
| 963 | |
| 964 | @retry_with_exponential_backoff(max_retries=5, initial_delay=2.0, max_delay=60.0) |
| 965 | def insert_comments_batch( |
| 966 | self, |
| 967 | comments: list[dict[str, Any]], |
| 968 | progress_callback: Callable[[int, int], None] | None = None, |
| 969 | auto_tune_batch_size: bool = True, |
| 970 | initial_batch_size: int = 1000, |
| 971 | ) -> tuple[int, int]: |
| 972 | """Insert multiple comments in optimized batches using PostgreSQL executemany. |
| 973 | |
| 974 | Args: |
| 975 | comments: List of comment dictionaries with Reddit JSON structure |
| 976 | progress_callback: Optional callback function(processed, total) for progress updates |
| 977 | auto_tune_batch_size: Whether to auto-tune batch size based on performance |
| 978 | initial_batch_size: Starting batch size (larger than SQLite) |
| 979 | |
| 980 | Returns: |
| 981 | Tuple of (successful_inserts, failed_inserts) |
| 982 | """ |
| 983 | if not comments: |
| 984 | return 0, 0 |
| 985 | |
| 986 | successful = 0 |
| 987 | failed = 0 |
| 988 | skipped = 0 # Track comments without valid IDs |
| 989 | total_comments = len(comments) |
| 990 | current_batch_size = initial_batch_size |
| 991 | |
| 992 | start_time = time.time() |
| 993 | |
| 994 | try: |
| 995 | with self.pool.get_connection() as conn: |
| 996 | # Disable synchronous_commit for bulk insert performance (10-20% faster) |
| 997 | # Transaction safety is maintained - rollback still works correctly |
| 998 | with conn.cursor() as cur: |
| 999 | cur.execute("SET LOCAL synchronous_commit = OFF") |
| 1000 | |
| 1001 | # Defer foreign key constraint validation to prevent batch-wide failures |
| 1002 | # This allows valid comments to succeed even if some reference missing posts |
| 1003 | # Invalid comments will be rejected at COMMIT time (not during INSERT) |
| 1004 | try: |
| 1005 | cur.execute("SET CONSTRAINTS comments_post_id_fkey DEFERRED") |
| 1006 | except Exception as e: |
| 1007 | # Constraint might not be deferrable yet (will be after schema update) |
| 1008 | print_warning( |
| 1009 | f"Could not defer foreign key constraint (this is expected before schema update): {e}" |
| 1010 | ) |
| 1011 | |
| 1012 | for batch_start in range(0, total_comments, current_batch_size): |
| 1013 | batch = comments[batch_start : batch_start + current_batch_size] |
| 1014 | batch_size = len(batch) |
| 1015 | |
| 1016 | batch_start_time = time.time() |
| 1017 | |
| 1018 | try: |
| 1019 | # Use PostgreSQL COPY protocol for true streaming (no buffering) |
| 1020 | copy_buffer = StringIO() |
| 1021 | records_prepared = 0 |
| 1022 |