perf: optimize drawings endpoint with caching and lazy loading

- Add 5s in-memory cache for /drawings responses with automatic cleanup
- Split Drawing/DrawingSummary types for efficient data fetching
- Implement lazy loading of drawing data in DrawingCard component
- Add configurable DRAWINGS_CACHE_TTL_MS and RATE_LIMIT_MAX_REQUESTS env vars
- Prevent memory leaks with periodic cleanup of cache and rate limit maps
- Add loading states and better UX for export operations
- Improve JSON parsing with error handling for malformed stored data

Benchmark results (100 drawings, cached):
- Avg latency: 6.94ms (p50: 4ms, p97.5: 8ms)
- Avg throughput: 668 req/s (peak: 1,023)
- 3k requests in 5s with 0 errors

Update .gitignore to exclude generated files, env files, and build artifacts
This commit is contained in:
Adrian Acala
2025-11-29 04:28:03 +00:00
parent 971046d568
commit 6f050aec7d
6 changed files with 348 additions and 57 deletions
+6 -6
View File
@@ -5,7 +5,7 @@ import { DrawingCard } from '../components/DrawingCard';
import { Plus, Search, Loader2, Inbox, Trash2, Folder, ArrowRight, Copy, Upload } from 'lucide-react';
import { useNavigate, useSearchParams, useLocation } from 'react-router-dom';
import * as api from '../api';
import type { Drawing, Collection } from '../types';
import type { DrawingSummary, Collection } from '../types';
import { useDebounce } from '../hooks/useDebounce';
import clsx from 'clsx';
import { ConfirmModal } from '../components/ConfirmModal';
@@ -45,7 +45,7 @@ export const Dashboard: React.FC = () => {
const [searchParams] = useSearchParams();
const location = useLocation();
const navigate = useNavigate();
const [drawings, setDrawings] = useState<Drawing[]>([]);
const [drawings, setDrawings] = useState<DrawingSummary[]>([]);
const [collections, setCollections] = useState<Collection[]>([]);
// Derived state from URL
@@ -309,12 +309,12 @@ export const Dashboard: React.FC = () => {
const handleImportDrawings = async (files: FileList | null) => {
if (!files || isTrashView) return;
const fileArray = Array.from(files);
const targetCollectionId = selectedCollectionId === undefined ? null : selectedCollectionId;
const result = await importDrawings(fileArray, targetCollectionId, refreshData);
if (result.failed > 0) {
setShowImportError({
isOpen: true,
@@ -810,7 +810,7 @@ export const Dashboard: React.FC = () => {
e.target.value = ''; // Reset input
}}
/>
<button
onClick={() => document.getElementById('dashboard-import')?.click()}
disabled={isTrashView}