Insert multiple posts in optimized batches using PostgreSQL COPY protocol. PostgreSQL COPY is 5-10x faster than INSERT statements for bulk loading. Target: 15,000+ posts/second vs SQLite's ~3,000/second. Args: posts: List of post dictionaries with Reddit JSON st
(
self,
posts: list[dict[str, Any]],
progress_callback: Callable[[int, int], None] | None = None,
auto_tune_batch_size: bool = True,
initial_batch_size: int = 1000,
)
| 763 | |
| 764 | @retry_with_exponential_backoff(max_retries=5, initial_delay=2.0, max_delay=60.0) |
| 765 | def insert_posts_batch( |
| 766 | self, |
| 767 | posts: list[dict[str, Any]], |
| 768 | progress_callback: Callable[[int, int], None] | None = None, |
| 769 | auto_tune_batch_size: bool = True, |
| 770 | initial_batch_size: int = 1000, |
| 771 | ) -> tuple[int, int, set[str]]: |
| 772 | """Insert multiple posts in optimized batches using PostgreSQL COPY protocol. |
| 773 | |
| 774 | PostgreSQL COPY is 5-10x faster than INSERT statements for bulk loading. |
| 775 | Target: 15,000+ posts/second vs SQLite's ~3,000/second. |
| 776 | |
| 777 | Args: |
| 778 | posts: List of post dictionaries with Reddit JSON structure |
| 779 | progress_callback: Optional callback function(processed, total) for progress updates |
| 780 | auto_tune_batch_size: Whether to auto-tune batch size based on performance |
| 781 | initial_batch_size: Starting batch size (larger than SQLite due to better concurrency) |
| 782 | |
| 783 | Returns: |
| 784 | Tuple of (successful_inserts, failed_inserts, failed_post_ids) |
| 785 | """ |
| 786 | if not posts: |
| 787 | return 0, 0, set() |
| 788 | |
| 789 | successful = 0 |
| 790 | failed = 0 |
| 791 | skipped = 0 # Track posts without valid IDs |
| 792 | failed_post_ids: set[str] = set() # Track which specific posts failed |
| 793 | total_posts = len(posts) |
| 794 | current_batch_size = initial_batch_size |
| 795 | |
| 796 | start_time = time.time() |
| 797 | |
| 798 | try: |
| 799 | with self.pool.get_connection() as conn: |
| 800 | # Disable synchronous_commit for bulk insert performance (10-20% faster) |
| 801 | # Transaction safety is maintained - rollback still works correctly |
| 802 | with conn.cursor() as cur: |
| 803 | cur.execute("SET LOCAL synchronous_commit = OFF") |
| 804 | |
| 805 | for batch_start in range(0, total_posts, current_batch_size): |
| 806 | batch = posts[batch_start : batch_start + current_batch_size] |
| 807 | batch_size = len(batch) |
| 808 | |
| 809 | batch_start_time = time.time() |
| 810 | |
| 811 | try: |
| 812 | # Use PostgreSQL COPY protocol for true streaming (no buffering) |
| 813 | # This eliminates 350MB buffer overhead per batch |
| 814 | copy_buffer = StringIO() |
| 815 | records_prepared = 0 |
| 816 | |
| 817 | for post in batch: |
| 818 | # Validate post has a valid ID before attempting insertion |
| 819 | post_id = post.get("id") |
| 820 | if not post_id or (isinstance(post_id, str) and not post_id.strip()): |
| 821 | skipped += 1 |
| 822 | continue |