لماذا التخزين المؤقت المتقدم ضرورة لتطبيقات TypeScript
\n\nفي الأنظمة الحديثة عالية التحميل، لم يعد التخزين المؤقت خيارًا - بل أصبح عنصرًا إلزاميًا في البنية. لمطوري TypeScript الذين يعملون مع Node.js، يمكن أن يؤدي اختيار استراتيجية التخزين المؤقت الصحيحة إلى تقليل وقت استجابة API من 200 مللي ثانية إلى 2 مللي ثانية وتقليل الحمل على قاعدة البيانات بنسبة 80-90%. في هذه المقالة، سننظر في ثلاثة أساليب للتخزين المؤقت على مستوى متقدم: Redis وMemcached والحلول المدمجة في TypeScript.
\n\n1. التخزين المؤقت المدمج في TypeScript: من Map البسيط إلى LRU-cache
\n\nالحلول المدمجة مثالية للتطبيقات المتجانسة أو الخدمات المصغرة حيث لا يلزم وجود ذاكرة تخزين مؤقت موزعة. الخيار الأكثر بدائية هو استخدام Map، لكنه غير مناسب للإنتاج بسبب عدم وجود TTL والتحكم في الذاكرة.
تنفيذ LRU-cache مع TTL
\n\nلننظر في تنفيذ متقدم لذاكرة التخزين المؤقت LRU (الأقل استخدامًا مؤخرًا) مع التنظيف التلقائي:
\n\ninterface CacheEntry<T> {\n value: T;\n expiresAt: number;\n}\n\nclass LRUCache<T> {\n private cache: Map<string, CacheEntry<T>>;\n private readonly maxSize: number;\n private readonly ttlMs: number;\n\n constructor(maxSize: number = 100, ttlMs: number = 60000) {\n this.cache = new Map();\n this.maxSize = maxSize;\n this.ttlMs = ttlMs;\n }\n\n get(key: string): T | undefined {\n const entry = this.cache.get(key);\n if (!entry) return undefined;\n\n if (Date.now() > entry.expiresAt) {\n this.cache.delete(key);\n return undefined;\n }\n\n // نقل المفتاح إلى النهاية (الأحدث)\n this.cache.delete(key);\n this.cache.set(key, entry);\n return entry.value;\n }\n\n set(key: string, value: T): void {\n if (this.cache.has(key)) {\n this.cache.delete(key);\n } else if (this.cache.size >= this.maxSize) {\n // حذف العنصر الأقدم (الأول)\n const oldestKey = this.cache.keys().next().value;\n this.cache.delete(oldestKey);\n }\n\n this.cache.set(key, {\n value,\n expiresAt: Date.now() + this.ttlMs,\n });\n }\n\n clear(): void {\n this.cache.clear();\n }\n}\n\n// الاستخدام\nconst userCache = new LRUCache<User>(500, 300000); // 500 عنصر، TTL 5 دقائق\n\nنمط متقدم: مصمم التخزين المؤقت
\n\nلنظافة الكود نستخدم مصممات TypeScript:
\n\nfunction Cached(ttlMs: number = 60000) {\n const cache = new LRUCache<any>(100, ttlMs);\n\n return function (\n target: any,\n propertyKey: string,\n descriptor: PropertyDescriptor\n ) {\n const originalMethod = descriptor.value;\n\n descriptor.value = async function (...args: any[]) {\n const key = `${propertyKey}_${JSON.stringify(args)}`;\n const cached = cache.get(key);\n if (cached !== undefined) return cached;\n\n const result = await originalMethod.apply(this, args);\n cache.set(key, result);\n return result;\n };\n\n return descriptor;\n };\n}\n\nclass UserService {\n @Cached(120000) // دقيقتان\n async getUser(id: string): Promise<User> {\n // استعلام مكلف لقاعدة البيانات\n return db.findUser(id);\n }\n}\n\n2. Redis: تخزين مؤقت موزع مع هياكل بيانات متقدمة
\n\nRedis ليس مجرد مخزن key-value. دعمه لهياكل البيانات المعقدة (Hash, List, Set, Sorted Set) يفتح إمكانيات لتنفيذ استراتيجيات تخزين مؤقت متقدمة.
\n\nالتخزين المؤقت مع الإبطال بالعلامات
\n\nالمشكلة: عند تحديث كيان واحد، يجب إبطال عدة ذاكرات تخزين مؤقت. الحل - العلامات:
\n\nimport Redis from 'ioredis';\n\nclass TaggedCache {\n private redis: Redis;\n private readonly keyPrefix: string;\n\n constructor(redisUrl: string, prefix: string = 'cache') {\n this.redis = new Redis(redisUrl);\n this.keyPrefix = prefix;\n }\n\n async get<T>(key: string,