Computer >> 컴퓨터 >  >> 프로그래밍 >> Redis

SQL 성능 향상:Drizzle ORM 쿼리를 위한 Upstash Redis 캐싱

얼마 전 Drizzle ORM과 협업할 기회가 있었습니다.

이 TypeScript ORM이 커뮤니티에서 얼마나 큰 사랑을 받는지 보고 "예 😳"라고 답하기로 한 결정은 당연한 결정이었습니다.

SQL 성능 향상:Drizzle ORM 쿼리를 위한 Upstash Redis 캐싱

이 기사에서는 Upstash Redis x Drizzle 캐싱 통합이 어떻게 SQL 성능을 향상하는지, 그리고 Lua 스크립트와 해시 데이터 구조를 사용하여 통합을 최적화하는 방법을 살펴보겠습니다.

과제:최신 애플리케이션의 SQL 성능

기존 SQL 데이터베이스는 일관성과 복잡한 관계 모델링에 뛰어나지만 다음과 같은 문제로 어려움을 겪을 수 있습니다.

  • 긴 지연 시간 분산 환경에서
  • 연결 풀링 제한 서버리스 기능에서
  • 반복적인 쿼리 오버헤드 자주 액세스하는 데이터에 대한
  • 병목 현상 확장 읽기 부하가 심한 경우

해결책? 데이터 관계를 이해하고 캐시 무효화를 자동으로 관리하는 캐싱 레이어입니다.

Upstash x Drizzle 캐싱 작동 방식

읽기 성능 향상:대체를 통한 캐시 우선

Drizzle 캐싱이 활성화된 상태에서 쿼리를 실행하면 통합이 먼저 Redis에서 캐시된 결과를 확인합니다.

  • 캐시 누락 :찾을 수 없는 경우 데이터베이스에서 쿼리를 읽습니다. 결과는 종속 테이블에 대한 메타데이터와 함께 Redis에 저장됩니다.
  • 캐시 적중 :관계형 데이터베이스에서 읽을 필요 없이 후속 동일한 쿼리가 Redis에서 즉시 반환됩니다.
// This query checks Redis first and only reads from the database if needed
const users = await db.select().from(usersTable)
 .where(eq(usersTable.status, 'active'))
 .$withCache();

쓰기 작업에 대한 스마트 무효화

쓰기 작업 중에 마법이 발생합니다. 관계형 데이터베이스의 데이터를 수정하면 자동으로 통합됩니다.

  1. 의존성 식별 :수정된 테이블에 의존하는 캐시된 쿼리를 결정합니다.
  2. 일괄 무효화 :영향을 받은 모든 캐시 항목을 제거합니다.
// This insert automatically invalidates all cached queries that depend on usersTable
await db.insert(usersTable).values({ 
 email: 'new@user.com', 
 status: 'active' 
});

간단한 캐시 구축:"순진한" 접근 방식

캐싱 통합으로 해결되는 문제를 이해하기 위해 가장 간단한 구현부터 시작해 보겠습니다. 쿼리 결과를 캐시할 때 다음을 수행해야 합니다.

  1. 캐시된 값 저장
  2. 이 쿼리가 무효화를 위해 의존하는 테이블 추적

간단한 캐시 저장

// When adding an item to the cache
await redis.set(itemHash, cachedValue);
await Promise.all(
 dependentTables.map((table) => redis.sadd(table, itemHash))
);

이 접근 방식은 캐시된 결과를 키-값 쌍으로 저장하고 각 종속 테이블의 이름을 딴 집합에 항목 해시를 추가하여 종속성을 추적합니다.

간단한 캐시 무효화

// When invalidating based on table changes
const hashesToInvalidate = await redis.sunion(dependentTables);
await redis.del(...hashesToInvalidate);

이는 수정된 테이블에 의존하는 캐시된 항목을 모두 찾은 다음 삭제하는 방식으로 작동합니다.

이러한 "순진한" 접근 방식의 문제점

기술적으로는 작동하지만 이 순진한 구현에는 두 가지 성능 문제가 있습니다.

문제 1:여러 번의 왕복

무효화 프로세스에는 두 가지 별도의 Redis 작업이 필요합니다. :

  1. 먼저 SUNION을 호출합니다. 삭제할 키 목록을 얻으려면
  2. 그런 다음 DEL을 호출합니다. 1단계의 결과로

이로 인해 왕복 종속성이 생성됩니다. 여기서 두 번째 작업은 첫 번째 작업이 완료될 때까지 기다려야 합니다.

문제 2:느린 대량 삭제

DEL 많은 키를 무효화할 때 명령이 병목 현상을 일으킬 수 있습니다:

// This could potentially delete thousands of keys
await redis.del(...hashesToInvalidate);

users와 같은 인기 있는 테이블이 있는 경우 수백 개의 캐시된 쿼리에서 참조되는 단일 업데이트로 수백 개의 개별 Redis 키가 삭제될 수 있습니다. 수백 또는 수천 개의 키를 사용하면 속도가 너무 느려질 수 있습니다.

해결책 1:Lua 스크립트

Upstash Redis는 Lua 스크립트 평가를 완벽하게 지원합니다.

Lua 스크립트는 서버 측에서 여러 Redis 명령을 실행하여 왕복 문제를 해결합니다.

-- Invalidation script that combines SUNION and DEL
local tables = KEYS -- table names passed as keys
local keysToDelete = {}
 
if #tables > 0 then
 -- Get all hashes that depend on these tables
 local hashesToInvalidate = redis.call('SUNION', unpack(tables))
 
 -- Prepare for deletion
 for _, hash in ipairs(hashesToInvalidate) do
 keysToDelete[#keysToDelete + 1] = hash
 end
 
 -- Add table sets themselves to deletion list
 for _, table in ipairs(tables) do
 keysToDelete[#keysToDelete + 1] = table
 end
 
 -- Single atomic deletion
 if #keysToDelete > 0 then
 redis.call('DEL', unpack(keysToDelete))
 end
end

Lua 스크립트의 이점:

  • 편도 왕복 :모든 작업은 서버측에서 발생합니다
  • 지연 시간 감소 :작업 간 네트워크 오버헤드 없음
  • 일관성 :네트워크 문제로 인한 부분 업데이트 위험 없음

해결책 2:효율적인 삭제를 위한 해시 기반 저장

Lua 스크립트를 사용하더라도 수백 개의 개별 키를 삭제하는 것은 우리가 원하는 것보다 느릴 수 있습니다. Redis 해시는 훨씬 더 효율적인 솔루션을 제공합니다:

해시 기반 접근 방식

캐시된 각 쿼리를 별도의 Redis 키로 저장하는 대신 동일한 테이블에 따른 쿼리를 해시로 그룹화합니다.

// Old approach: Each query gets its own key
await redis.set('query_hash_1', result1);
await redis.set('query_hash_2', result2);
await redis.set('query_hash_3', result3);
 
// New approach: Group queries by table dependencies
const compositeKey = 'users,posts'; // hash key for users and posts tables
await redis.hset(compositeKey, {
 'query_hash_1': result1,
 'query_hash_2': result2,
 'query_hash_3': result3
});

해시가 훨씬 빠른 이유

users에 의존하는 쿼리를 무효화하는 경우 테이블:

// Old way: Delete many individual keys (slow)
await redis.del('query_hash_1', 'query_hash_2', /* ...hundreds more... */);
 
// New way: Delete entire hash table (fast)
await redis.del('__CT__users,posts');

성능상의 이점:

  • 단일 삭제 작업 :DEL 1개 명령은 수백 개의 캐시된 쿼리를 제거합니다
  • 메모리 효율성 :Redis는 한 번의 작업으로 전체 해시 테이블을 해제할 수 있습니다.
  • 원자적 정리 :관련된 모든 검색어가 함께 무효화됩니다.

Lua 스크립트가 최종적으로 어떻게 보이는지 보려면 Drizzle 저장소에서 구현을 확인할 수 있습니다.

세밀한 제어를 위한 캐시 태그

테이블 기반 무효화 외에도 Drizzle은 세밀한 캐시 제어를 위한 사용자 정의 태그를 지원합니다.

// Cache with a custom tag
const premiumUsers = await db.select().from(usersTable)
 .where(eq(usersTable.plan, 'premium'))
 .$withCache({ tag: 'premium_users' });
 
// Later, invalidate just this specific query
await db.$cache?.invalidate({ tags: 'premium_users' });

자동 무효화와 수동 무효화

자동 무효화 (기본값):종속 테이블이 변경되면 쿼리가 무효화되어 데이터 일관성이 보장되지만 보다 적극적인 캐시 삭제가 이루어집니다.

수동 무효화 :최종 일관성이 허용되는 시나리오의 경우 자동 무효화를 비활성화하고 캐시를 지울 시기를 수동으로 제어할 수 있습니다.

// Won't be automatically invalidated - good for analytics data
const monthlyStats = await db.select()
 .from(analyticsTable)
 .$withCache({ autoInvalidate: false });
 
// Manually invalidate when needed (e.g., daily batch job)
await db.$cache?.invalidate({ tables: ['analyticsTable'] });

실제 사용 사례

이제 통합의 기술적 측면을 다루었으므로 이러한 개념이 어떻게 실제 애플리케이션으로 변환되는지 살펴보겠습니다.

전자상거래 제품 카탈로그

// Cache product listings with automatic invalidation
const products = await db.select()
 .from(productsTable)
 .where(eq(productsTable.active, true))
 .$withCache({ tag: 'active_products' });
 
// When inventory changes, cache is automatically invalidated
await db.update(productsTable)
 .set({ stock: newStock })
 .where(eq(productsTable.id, productId));

콘텐츠 관리

// Cache published articles with manual invalidation
const articles = await db.select()
 .from(articlesTable)
 .where(eq(articlesTable.status, 'published'))
 .$withCache({ 
 autoInvalidate: false,
 tag: 'published_articles'
 });
 
// Manually invalidate when content is updated
await db.$cache?.invalidate({ tags: 'published_articles' });

결론

Upstash Redis 및 Drizzle 캐싱 통합은 (매우) 최소한의 코드 변경으로 SQL 쿼리 성능을 대폭 향상하고 데이터베이스 부하를 줄일 수 있습니다.

캐시를 활성화하면 다음을 기대할 수 있습니다.

  • 획기적으로 빨라짐 캐시된 데이터에 대한 응답 시간 쿼리
  • 데이터베이스 부하 감소 확장성 향상

Upstash Redis의 글로벌 배포 및 종량제 가격을 갖춘 서버리스 우선 아키텍처는 현대 애플리케이션을 위한 훌륭한 기반입니다.

전자상거래 플랫폼, 분석 대시보드, 콘텐츠 관리 시스템 등에 적합합니다.

추가 자료

더 깊이 들어가고 싶나요? 다음은 제가 추천하는 몇 가지 유용한 자료입니다.

  • Upstash Redis 및 Drizzle 통합 가이드
  • Drizzle 캐싱 문서
  • Upstash Redis 시작하기
  • Upstash 비율 제한 SDK(TypeScript) - 최적의 성능을 위해 Lua 스크립트를 활용하는 또 다른 강력한 SDK
  • Upstash 비율 제한 SDK(Python) - Rate Limit SDK의 Python 구현.