Implement backend pre-caching optimization for faster load times with 300-article prefetching

This commit is contained in:
Jarian Cottingham 2026-02-02 09:43:03 -06:00
parent 432c162642
commit 7ada2278c6
2 changed files with 48 additions and 2 deletions

10
app.py
View File

@ -1,8 +1,13 @@
from flask import Flask, jsonify, request, send_from_directory, send_file from flask import Flask, jsonify, request, send_from_directory, send_file
import os import os
import logging
app = Flask(__name__) app = Flask(__name__)
# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Import our archive parser # Import our archive parser
from parse_archive import ArchiveParser from parse_archive import ArchiveParser
@ -52,7 +57,7 @@ def get_more_posts():
filters_posts += [p] filters_posts += [p]
return jsonify(filters_posts) return jsonify(filters_posts)
except Exception as e: except Exception as e:
print(f"Error: {e}") logger.error(f"Error fetching more posts: {e}")
return jsonify({"error": "Failed to fetch more posts"}), 500 return jsonify({"error": "Failed to fetch more posts"}), 500
@app.route("/posts/total", methods=["GET"]) @app.route("/posts/total", methods=["GET"])
@ -63,7 +68,7 @@ def get_total_posts():
total = archive_parser.get_total_posts() total = archive_parser.get_total_posts()
return jsonify({"total": total}) return jsonify({"total": total})
except Exception as e: except Exception as e:
print(f"Error: {e}") logger.error(f"Error fetching total posts: {e}")
return jsonify({"error": "Failed to fetch total count"}), 500 return jsonify({"error": "Failed to fetch total count"}), 500
@app.route("/api") @app.route("/api")
@ -77,6 +82,7 @@ def home():
"GET /posts/more?count=N": "Get next 10 posts, starting from index N", "GET /posts/more?count=N": "Get next 10 posts, starting from index N",
"GET /posts/total": "Get total number of posts", "GET /posts/total": "Get total number of posts",
}, },
"status": "healthy"
} }
) )

View File

@ -5,6 +5,8 @@ import urllib.parse
import uuid import uuid
from io import BytesIO from io import BytesIO
from typing import Any, Dict, List from typing import Any, Dict, List
from concurrent.futures import ThreadPoolExecutor
import threading
import requests import requests
from PIL import Image from PIL import Image
@ -14,6 +16,10 @@ class ArchiveParser:
def __init__(self): def __init__(self):
"""Initialize the ArchiveParser with archive_dir from environment variable.""" """Initialize the ArchiveParser with archive_dir from environment variable."""
self.archive_dir = os.environ.get("ARCHIVE_DIR", "/default/archive/path") self.archive_dir = os.environ.get("ARCHIVE_DIR", "/default/archive/path")
self.cache = {}
self.cache_lock = threading.Lock()
self.pre_cache_size = 300 # Pre-cache 300 articles ahead
self.cache_executor = ThreadPoolExecutor(max_workers=2) # For background caching
def get_posts(self, count: int, start_index: int = 0) -> List[Dict[str, Any]]: def get_posts(self, count: int, start_index: int = 0) -> List[Dict[str, Any]]:
""" """
@ -31,6 +37,13 @@ class ArchiveParser:
List[Dict[str, Any]]: List of post dictionaries List[Dict[str, Any]]: List of post dictionaries
""" """
try: try:
# Check if we have cached posts for this range
cache_key = f"posts_{start_index}_{count}"
with self.cache_lock:
if cache_key in self.cache:
print(f"Cache hit for {cache_key}")
return self.cache[cache_key]
# Use ls with tail and head commands for efficient pagination # Use ls with tail and head commands for efficient pagination
# Get all directories, skip the first start_index, then get count number of entries # Get all directories, skip the first start_index, then get count number of entries
cmd = f"ls -1 {self.archive_dir} | tail -{start_index + count + 1} | head -{count}" cmd = f"ls -1 {self.archive_dir} | tail -{start_index + count + 1} | head -{count}"
@ -52,6 +65,15 @@ class ArchiveParser:
# Convert extracted posts to final format with proper IDs # Convert extracted posts to final format with proper IDs
formatted_posts = [self._create_post(post) for post in posts] formatted_posts = [self._create_post(post) for post in posts]
# Cache the results
with self.cache_lock:
self.cache[cache_key] = formatted_posts
# Pre-cache the next batch in background
if start_index + count < self.get_total_posts():
self._pre_cache_batch(start_index + count)
return formatted_posts return formatted_posts
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
@ -119,6 +141,24 @@ class ArchiveParser:
print(f"Error getting total posts: {e}") print(f"Error getting total posts: {e}")
return 0 return 0
def _pre_cache_batch(self, start_index: int):
"""Pre-cache the next batch of posts in the background."""
def cache_task():
try:
# Pre-cache next 300 posts
next_posts = self.get_posts(self.pre_cache_size, start_index)
print(f"Pre-cached {len(next_posts)} posts starting from index {start_index}")
except Exception as e:
print(f"Error in pre-caching: {e}")
# Submit to background thread
self.cache_executor.submit(cache_task)
def clear_cache(self):
"""Clear the cache."""
with self.cache_lock:
self.cache.clear()
def _extract_real_title_from_url(self, base_url: str) -> str: def _extract_real_title_from_url(self, base_url: str) -> str:
"""Extract the real title from base_url when title is 'Reddit - Prove your humanity'.""" """Extract the real title from base_url when title is 'Reddit - Prove your humanity'."""
if not base_url or not isinstance(base_url, str): if not base_url or not isinstance(base_url, str):