Redis는 데이터를 주로 메모리에 저장하는 데이터 스토어입니다. 전통적인 데이터베이스보다 훨씬 빠르기 때문에 최근 몇 년간 큰 인기를 얻고 있습니다.
이 튜토리얼에서는 Redis의 작동 원리와 활용 시점, 설치 방법, 그리고 PHP 웹 애플리케이션에서 캐싱 시스템으로 활용하는 방법까지 기본기를 차근차근 배워보겠습니다.
Redis란 무엇인가?
Redis는 데이터베이스와 유사한 데이터 스토어이지만, 데이터를 주로 메모리(in-memory)에 저장한다는 점에서 다릅니다. 디스크에 데이터를 저장하는 전통적인 데이터베이스보다 훨씬 빠른 처리 속도를 자랑하며, 이러한 속도 덕분에 캐싱 도구로 널리 사용됩니다.
Redis는 키-값(key-value) 쌍 형태로 데이터를 저장하기 때문에 어떤 데이터 타입이든 저장할 수 있습니다. 문서나 행(row) 기반으로 데이터를 관리하는 전통적인 데이터베이스와 또 다른 점입니다.
Redis 데이터베이스를 거대한 JSON 객체라고 생각하면 이해하기 쉽습니다. 데이터베이스의 모든 것이 키-값 쌍으로 이루어져 있기 때문인데요, 이런 특성 때문에 구조화된 데이터를 저장하기에는 적합하지 않을 수 있습니다.
또한 Redis는 디스크에 데이터를 기록하여 영속성(persistence)을 확보할 수 있는 기능도 갖추고 있어서, 일반 데이터베이스처럼 사용할 수도 있습니다. 주기적으로 또는 모든 명령 실행 후 데이터를 저장하도록 설정할 수 있습니다. 만약 영속성 설정 없이 사용하면 데이터가 매우 휘발적이 되어, 시스템 장애 발생 시 데이터가 모두 사라질 수 있다는 점을 유의해야 합니다.
Redis는 프로덕션 수준의 애플리케이션에서 널리 활용되고 있으며, Twitter, GitHub, Snapchat, Stack Overflow 같은 대형 기업들도 사용하고 있습니다.
Redis가 빛을 발하는 대표적인 활용 사례
- 일회용 비밀번호(OTP): OTP는 한 번만 사용되고 수명이 짧은 경우가 많습니다. Redis는 데이터에 만료 시간을 설정할 수 있기 때문에, 일정 기간 후 삭제하는 것을 걱정하지 않고 안전하게 OTP를 저장할 수 있습니다.
- 자주 접근되는 리소스: 자주 변경되지 않지만 조회 빈도가 높은 데이터라면, Redis에 캐싱해두면 데이터베이스를 조회하거나 외부 서비스를 호출하는 데 드는 시간을 크게 아낄 수 있습니다.
- 무거운 쿼리: 실행 시간이 오래 걸리고 결과가 자주 바뀌지 않는 데이터베이스 쿼리라면, 그 결과를 Redis에 원하는 기간 동안 저장해 두어 응답 시간을 획기적으로 줄일 수 있습니다.
Redis 설치 방법
Redis는 어떤 운영체제에서든 설치할 수 있습니다. 여기서는 macOS, WSL(Windows Subsystem for Linux), Linux 환경별 설치 방법을 살펴보겠습니다.
macOS
macOS에서는 Homebrew를 통해 다음 명령어로 Redis를 설치합니다:
brew install redis설치가 완료되면 아래 명령어로 Redis 서버를 시작합니다:
redis-serverWindows Subsystem for Linux 및 Linux
아쉽게도 Redis는 Windows 운영체제를 공식적으로 지원하지 않습니다. 따라서 Windows 사용자라면 WSL(Windows Subsystem for Linux)을 설치하여 Linux 환경을 구성한 뒤 Redis를 사용해야 합니다.
Linux 환경에서는 다음 명령어로 Redis를 설치합니다:
curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/redis.list
sudo apt-get update
sudo apt-get install redis설치 후 아래 명령어로 Redis 서버를 시작합니다:
sudo service redis-server startRedis 설치가 끝나면 redis-cli ping 명령어로 정상 작동 여부를 확인할 수 있습니다. 정상이라면 "PONG"이라는 응답이 출력됩니다.
Redis 기본 사용법
REPL 환경이나 독립 실행형 애플리케이션으로 Redis를 사용하려면 redis-cli 명령어를 실행하면 됩니다. 이 명령은 REPL 환경을 열어줍니다.
데이터 저장하기 (SET)
Redis에서는 SET 키워드로 키-값 쌍을 저장합니다. 예를 들어 username 키에 Zubs 값을 저장하려면 다음과 같이 입력합니다:
SET username Zubs데이터 조회하기 (GET)
방금 저장한 username 키의 값을 가져오려면 GET 키워드를 사용합니다:
GET username데이터 삭제하기 (DEL)
저장된 키는 DEL 키워드로 삭제할 수 있습니다:
DEL username키 존재 여부 확인하기 (EXISTS)
EXISTS 키워드로 특정 키의 존재 여부를 확인할 수 있습니다. 키가 존재하지 않으면 0, 존재하면 1을 반환합니다. 방금 삭제한 username 키로 직접 확인해 보세요.
키에 만료 시간(TTL) 설정하기
Redis의 가장 강력한 기능 중 하나는 키 생성 시 유효 기간을 지정할 수 있다는 점입니다. SETEX 키워드를 사용하면 됩니다:
SETEX key seconds value특정 키의 남은 유효 시간은 TTL 키워드로 확인할 수 있습니다. 반환 값은 다음과 같습니다:
-1: 만료 시간이 설정되지 않아 무기한 저장되는 상태-2: 해당 키가 존재하지 않음- 양수: 남은 유효 시간(초 단위)
이미 만료 시간 없이 생성된 키에는 EXPIRE 키워드로 나중에 만료 시간을 초 단위로 지정할 수 있습니다. 예를 들어, 값이 26인 age 키를 먼저 생성해 보겠습니다:
SET age 26그다음 20초의 만료 시간을 설정합니다:
EXPIRE age 20TTL 명령을 반복해서 실행해 보면 남은 시간이 줄어들다가 결국 키가 사라지는 것을 확인할 수 있습니다.
Redis로 간단한 애플리케이션 만들기
Redis의 작동 방식을 제대로 이해하기 위해, Redis로 데이터를 캐싱하여 응답 속도를 높이는 기본적인 웹 애플리케이션을 만들어 보겠습니다. JSONPlaceholder에서 이미지 데이터를 가져와 반환하는 간단한 애플리케이션입니다.
Composer로 새 PHP 프로젝트 생성하기
프로젝트용 폴더를 새로 만들고 해당 폴더로 이동한 뒤, 다음 명령어로 Composer 프로젝트를 생성합니다:
composer init -q이 명령을 실행하면 다음과 같은 composer.json 파일이 생성됩니다:
{
"require": {}
}다음으로 외부에 공개되는 코드 파일을 담을 public 폴더를 만들고, 그 안에 새 index.php 파일을 생성합니다.
일단 PHP 파일에 기본 코드를 넣고 서버를 시작해 봅시다:
<?php
echo "Hello World!";php -S localhost:8080라우터 설치 및 요청 처리하기
프로젝트를 완성하기 위해 간단한 PHP 라우터인 AltoRouter와 HTTP 클라이언트인 GuzzleHttp를 설치합니다:
composer require altorouter/altorouter guzzlehttp/guzzleindex.php 파일을 아래 코드로 업데이트합니다:
<?php
// Composer autoload 파일 불러오기
require_once __DIR__ . '/../vendor/autoload.php';
// GuzzleHttp Client 임포트
use GuzzleHttp\Client;
// 라우터와 웹 클라이언트 인스턴스 생성
$router = new AltoRouter();
$client = new Client();
// 샘플 라우트 등록
$router->map('GET', '/', function () {
// 응답 Content-Type 설정
header('Content-Type: application/json; charset=utf-8');
// 기본 응답 반환
echo json_encode(['data' => 'Hello World']);
});
/**
* 모든 사진을 가져오는 라우트
*/
$router->map('GET', '/photos', function () use ($client) {
// JSONPlaceholder에 요청 전송
$response = $client->request('GET', 'https://jsonplaceholder.typicode.com/photos');
header('Content-Type: application/json; charset=utf-8');
echo json_encode([
'data' => json_decode($response->getBody()->getContents())
]);
});
/**
* ID로 단일 사진을 가져오는 라우트
*/
$router->map('GET', '/photos/[i:id]', function (int $id) use ($client) {
$response = $client->request('GET', 'https://jsonplaceholder.typicode.com/photos/' . $id);
header('Content-Type: application/json; charset=utf-8');
echo json_encode([
'data' => json_decode($response->getBody()->getContents())
]);
});
$match = $router->match();
if( is_array($match) && is_callable( $match['target'] ) ) {
call_user_func_array( $match['target'], $match['params'] );
} else {
// 매칭되는 라우트가 없는 경우
header( $_SERVER["SERVER_PROTOCOL"] . ' 404 Not Found');
}코드가 비교적 직관적이지만, 이해를 돕기 위해 간략히 설명하겠습니다. 1~11번째 줄에서는 필요한 GuzzleHttp와 AltoRouter 클래스를 임포트하고 인스턴스를 생성합니다.
14~20번째 줄에서는 "Hello World!"를 반환하는 간단한 클로저로 첫 번째 라우트를 등록합니다. 25~45번째 줄에서는 두 개의 라우트를 추가로 등록하는데, 하나는 전체 사진 목록을 가져오는 /photos, 다른 하나는 특정 사진 하나를 가져오는 /photos/id입니다.
마지막 부분은 라우터 패키지 공식 문서에 따라, 라우트 선언 시 설정된 클로저를 실제로 실행하기 위해 반드시 필요한 코드입니다.
Postman으로 각 라우트를 테스트해 볼 수 있습니다.
테스트 결과, /photos 라우트는 요청당 평균 1400ms, /photos/id 라우트는 평균 900ms가 소요됩니다.
Redis 설치 및 인스턴스 생성하기
JSONPlaceholder에 매번 요청을 보내는 대신, 첫 요청의 결과를 캐시에 저장한 뒤 이후 요청부터는 캐시에서 응답을 반환하면 이 시간을 크게 단축할 수 있습니다.
PHP에서 Redis를 사용하려면 PhpRedis 확장 모듈을 설치해야 합니다. 이 확장은 Redis와 통신할 수 있는 API를 제공합니다. 다음 명령어로 간단히 설치할 수 있습니다:
pecl install redis설치 후에는 PHP 프로젝트에서 해당 클래스를 사용할 수 있습니다. index.php 파일 상단에서 클래스를 임포트하고 인스턴스를 생성합니다:
$redis = new Redis();
$redis->connect('127.0.0.1');이제 프로젝트에서 Redis를 자유롭게 사용할 수 있습니다.
Redis로 데이터 캐싱하기
JSONPlaceholder에서 받은 원본 JSON 응답을 만료 시간 1시간(3600초)으로 설정하여 Redis에 저장해 보겠습니다:
$response = $client->request('GET', 'https://jsonplaceholder.typicode.com/photos');
$redis->setex(
'photos',
3600,
$response->getBody()->getContents()
);여기서는 photos라는 새 키를 만들고, 만료 시간을 1시간으로 지정한 뒤, JSONPlaceholder에서 받은 원본 응답을 해당 키에 할당했습니다.
하지만 이 상태에서는 API 응답 속도가 여전히 느립니다. 단순히 응답을 저장만 했을 뿐, Redis를 활용해 응답을 반환하지 않았기 때문입니다.
이를 해결하려면, 새 요청이 들어올 때마다 메모리에 저장된 데이터가 있는지 먼저 확인해야 합니다. 데이터가 있으면 메모리에서 즉시 반환하고, 없으면 JSONPlaceholder에 새로 호출하는 방식입니다.
/photos 블록을 아래처럼 수정합니다:
/**
* 모든 사진을 가져오는 라우트
*/
$router->map('GET', '/photos', function () use ($client, $redis) {
// Redis에 해당 키가 있는지 확인
if (!$redis->exists('photos')) {
$response = $client->request('GET', 'https://jsonplaceholder.typicode.com/photos');
// 다음 사용을 위해 데이터 저장
$redis->setex(
'photos',
REDIS_STANDARD_EXPIRY,
$response->getBody()->getContents()
);
}
header('Content-Type: application/json; charset=utf-8');
echo json_encode([
'data' => json_decode($redis->get('photos'))
]);
});Postman에서 다시 테스트해 보면, 첫 호출(캐싱되기 전 원본 호출) 이후 /photos 라우트의 평균 응답 시간이 약 20ms로 떨어진 것을 확인할 수 있습니다. 무려 50배 이상의 성능 개선입니다. Redis는 상당한 처리 시간과 리소스를 절약해 줍니다.
/photos/id 라우트도 Redis를 활용하도록 수정합니다:
$router->map('GET', '/photos/[i:id]', function (int $id) use ($client, $redis) {
if (!$redis->exists('photos:' . $id)) {
$response = $client->request('GET', 'https://jsonplaceholder.typicode.com/photos/' . $id);
$redis->setex(
'photos:' . $id,
REDIS_STANDARD_EXPIRY,
$response->getBody()->getContents()
);
}
header('Content-Type: application/json; charset=utf-8');
echo json_encode([
'data' => json_decode($redis->get('photos:' . $id))
]);
});/photos/id 라우트 역시 응답 시간이 5ms 미만으로 단축되어, 약 45배 이상의 성능 향상을 이루었습니다.
마무리
이제 Redis가 무엇인지, 기본 사용법은 어떻게 되는지, 그리고 이를 활용해 PHP 웹 애플리케이션의 속도를 어떻게 개선할 수 있는지 이해하게 되셨기를 바랍니다. 이 글에서 사용된 코드 파일은 GitHub에서 확인할 수 있습니다.
궁금한 점이나 추가 조언이 있다면 언제든지 연락해 주세요. 더 많은 글을 읽거나 저의 작업을 팔로우하고 싶으시다면 LinkedIn, Twitter, GitHub에서 저와 연결하실 수 있습니다. 빠르고, 간편하고, 무료입니다!
freeCodeCamp의 오픈소스 커리큘럼은 4만 명 이상의 사람들이 개발자로 취업하는 데 도움을 주었습니다. 무료로 코딩을 배워보세요!