Memorystore로 데이터 캐싱

확장 가능한 고성능 웹 애플리케이션은 특정 태스크에 대해 분산형 메모리 내 데이터 캐시를 견고한 영구 스토리지 앞에 두거나 아예 대체하여 사용하는 경우가 많습니다. Redis용 Memorystore를 캐싱 서비스로 사용하는 것이 좋습니다. Redis용 Memorystore는 무료 등급을 제공하지 않습니다. 자세한 내용은 Memorystore 가격 책정을 참조하세요.

시작하기 전에 앱이 Redis용 Memorystore 할당량 내에 있는지 확인합니다.

메모리 캐시를 사용하는 경우

세션 데이터, 사용자 환경설정, 웹페이지의 쿼리를 통해 반환되는 기타 데이터도 캐시하기에 적절한 대상입니다. 일반적으로 자주 실행되는 쿼리가 앱에 즉시 표시할 필요가 없는 일련의 결과를 반환하는 경우 이 결과를 캐시할 수 있습니다. 후속 요청에서는 캐시를 확인하여 결과가 없거나 만료된 경우에만 데이터베이스를 쿼리합니다.

다른 영구 스토리지에 백업하지 않고 Memcache에만 값을 저장할 경우 값이 만료되어 캐시에서 삭제되면 애플리케이션이 정상적으로 동작하는지 확인해야 합니다. 예를 들어 사용자의 세션 데이터가 갑자기 사라져서 세션이 오작동하는 경우 세션 데이터를 Memorystore 외에 데이터베이스에도 저장해야 합니다.

Memorystore 권한 이해하기

Google Cloud 서비스와의 모든 상호작용은 승인을 받아야 합니다. 예를 들어 Memorystore에서 호스팅되는 Redis 데이터베이스와 상호작용하려면 앱에서 Memorystore에 액세스하도록 승인된 계정의 사용자 인증 정보를 제공해야 합니다.

기본적으로 앱은 앱과 동일한 프로젝트에 있는 데이터베이스에 액세스하도록 승인된 App Engine 기본 서비스 계정의 사용자 인증 정보를 제공합니다.

다음 조건 중 하나라도 해당하는 경우 사용자 인증 정보를 명시적으로 제공하는 대체 인증 기술을 사용해야 합니다.

  • 앱과 Memorystore 데이터베이스가 다른 Google Cloud 프로젝트에 있는 경우

  • 기본 App Engine 서비스 계정에 할당된 역할이 변경된 경우

대체 인증 기술에 대한 자세한 내용은 서버 간 프로덕션 애플리케이션용 인증 설정을 참조하세요.

Memorystore 사용 개요

앱에서 Memorystore를 사용하려면 다음 안내를 따르세요.

  1. Redis용 Memorystore를 설정합니다. 이를 위해서는 Memorystore에 Redis 인스턴스를 만들고 앱에서 Redis 인스턴스와 통신하는 데 사용하는 서버리스 VPC 액세스를 만들어야 합니다.

  2. Redis용 클라이언트 라이브러리를 설치하고 Redis 명령어를 사용하여 데이터를 캐시합니다.

    Redis용 Memorystore는 모든 Redis용 클라이언트 라이브러리와 호환됩니다.

    Go

    이 가이드에서는 redigo 클라이언트 라이브러리를 사용하여 앱에서 Redis 명령어를 전송하는 방법을 설명합니다.

    자바

    이 가이드에서는 Jedis 클라이언트 라이브러리를 사용하여 앱에서 Redis 명령어를 전송하는 방법을 설명합니다. Jdis 사용 방법에 대한 자세한 내용은 Jedis 위키를 참조하세요.

    Node.js

    이 가이드에서는 node_redis 클라이언트 라이브러리를 사용하여 앱에서 Redis 명령어를 전송하는 방법을 설명합니다.

    PHP

    이 가이드에서는 PHPRedis 클라이언트 라이브러리를 사용하여 앱에서 Redis 명령어를 전송하는 방법을 설명합니다.

    Python

    이 가이드에서는 redis-py 3.0 클라이언트 라이브러리를 사용하여 앱에서 Redis 명령어를 전송하는 방법을 설명합니다.

    Ruby

    이 가이드에서는 redis-rb 클라이언트 라이브러리를 사용하여 앱에서 Redis 명령어를 전송하는 방법을 설명합니다.

  3. 업데이트를 테스트합니다.

  4. App Engine에 앱을 배포합니다.

Redis용 Memorystore 설정

Redis용 Memorystore를 설정하려면 다음 안내를 따르세요.

  1. Memorystore에서 Redis 인스턴스를 만듭니다.

    Redis 인스턴스의 리전을 선택하라는 메시지가 표시되면 App Engine 앱이 있는 리전과 동일한 리전을 선택합니다.

  2. 만들려는 Redis 인스턴스의 IP 주소와 포트 번호를 기록합니다. 이 정보는 코드에서 Redis 클라이언트를 만들 때 사용됩니다.

  3. App Engine을 VPC 네트워크에 연결합니다. 앱은 VPC 커넥터를 통해서만 Memorystore와 통신할 수 있습니다.

    앱에서 커넥터를 사용하도록 구성의 설명대로 VPC 연결 정보를 app.yaml 파일에 추가해야 합니다.

종속 항목 설치

Go

App Engine에서 앱을 실행할 때 redigo 클라이언트 라이브러리를 앱에서 사용할 수 있도록 만들려면 라이브러리를 앱의 종속 항목에 추가합니다. 예를 들어 go.mod 파일을 사용하여 종속 항목을 선언하려면 다음 줄을 go.mod 파일에 추가합니다.

github.com/GoogleCloudPlatform/golang-samples/tree/master/memorystore/redis 모듈

Go 앱의 종속 항목 지정에 대해 자세히 알아보세요.

자바

App Engine에서 앱을 실행할 때 Jedis 클라이언트 라이브러리를 앱에서 사용할 수 있도록 하려면 앱의 종속 항목에 라이브러리를 추가합니다. 예를 들어 Maven을 사용하는 경우 pom.xml 파일에 다음 종속 항목을 추가합니다.

<dependency>
  <groupId>redis.clients</groupId>
  <artifactId>jedis</artifactId>
  <version>5.1.0</version>
</dependency>

Node.js

App Engine에서 앱을 실행할 때 node_redis 클라이언트 라이브러리를 앱에서 사용할 수 있도록 만들려면 라이브러리를 앱의 package.json 파일에 추가합니다.

예를 들면 다음과 같습니다.

{
  "name": "memorystore-redis",
  "description": "An example of using Memorystore(Redis) with Node.js",
  "version": "0.0.1",
  "private": true,
  "license": "Apache Version 2.0",
  "author": "Google Inc.",
  "engines": {
    "node": ">=16.0.0"
  },
  "dependencies": {
    "redis": "^4.0.0"
  }
}

Node.js 앱의 종속 항목 지정에 대해 자세히 알아보세요.

PHP

App Engine에서 앱을 실행할 때 PHPRedis 클라이언트 라이브러리를 앱에서 사용할 수 있도록 하려면 앱의 php.ini 파일에 redis.so 확장 프로그램을 추가합니다. 예를 들면 다음과 같습니다.

; Enable the Redis extension on App Engine
extension=redis.so

App Engine에서 PHP 확장 프로그램을 사용하는 방법은 동적으로 로드 가능한 확장 프로그램을 참조하세요.

Python

App Engine에서 앱을 실행할 때 앱에 redis-py 클라이언트 라이브러리를 앱에서 사용할 수 있도록 만들려면 앱의 requirements.txt 파일에 다음 줄을 추가합니다.

  redis

App Engine Python 3 런타임은 앱을 배포할 때 앱의 requirements.txt 파일을 자동으로 업로드합니다.

로컬 개발의 경우 venv와 같은 가상 환경에 종속 항목을 설치하는 것이 좋습니다.

Ruby

App Engine에서 앱을 실행할 때 redis-rb 클라이언트 라이브러리를 앱에서 사용할 수 있도록 만들려면 라이브러리를 앱의 Gemfile 파일에 추가합니다.

  source "https://cloud.google.com/memorystore"

  gem "redis-rb"

Redis 클라이언트 만들기

Redis 데이터베이스와 상호작용하려면 코드에서 Redis 클라이언트를 만들어 Redis 데이터베이스에 대한 연결을 관리해야 합니다. 다음 섹션에서는 Redis 클라이언트 라이브러리를 사용하여 Redis 클라이언트를 만드는 방법을 설명합니다.

환경 변수 지정

Redis 클라이언트 라이브러리는 환경 변수 2개를 사용하여 Redis 데이터베이스의 URL을 어셈블합니다.

  • Memorystore에 만든 Redis 데이터베이스의 IP 주소를 식별하는 변수입니다.
  • Memorystore에 만든 Redis 데이터베이스의 포트 번호를 식별하는 변수입니다.

이러한 변수를 코드에서 직접 정의하는 대신 앱의 app.yaml 파일에서 정의하는 것이 좋습니다. 이렇게 하면 로컬 환경 및 App Engine과 같은 다양한 환경에서 앱을 간편하게 실행할 수 있습니다. app.yaml 참조 페이지에서 환경 변수에 대해 자세히 알아보세요.

Go

예를 들어 app.yaml 파일에 다음 줄을 추가합니다.

  env_variables:
       REDISHOST: '10.112.12.112'
       REDISPORT: '6379'

자바

예를 들어 app.yaml 파일에 다음 줄을 추가합니다.

  env_variables:
       redis.host: '10.112.12.112'
       redis.port: '6379'

Node.js

예를 들어 app.yaml 파일에 다음 줄을 추가합니다.

  env_variables:
       REDISHOST: '10.112.12.112'
       REDISPORT: '6379'

PHP

예를 들어 app.yaml 파일에 다음 줄을 추가합니다.

  env_variables:
       REDIS_HOST: '10.112.12.112'
       REDIS_PORT: '6379'

Python

예를 들어 app.yaml 파일에 다음 줄을 추가합니다.

  env_variables:
       REDISHOST: '10.112.12.112'
       REDISPORT: '6379'

Ruby

예를 들어 app.yaml 파일에 다음 줄을 추가합니다.

  env_variables:
       REDISHOST: '10.112.12.112'
       REDISPORT: '6379'

Redis 가져오기 및 클라이언트 만들기

Go

REDISHOSTREDISPORT 환경 변수를 정의한 후 다음 줄을 사용하여 redigo 라이브러리를 가져오고 연결 풀을 만든 후 풀에서 Redis 클라이언트를 검색합니다.


// Command redis is a basic app that connects to a managed Redis instance.
package main

import (
	"fmt"
	"log"
	"net/http"
	"os"

	"github.com/gomodule/redigo/redis"
)

var redisPool *redis.Pool

func incrementHandler(w http.ResponseWriter, r *http.Request) {
	conn := redisPool.Get()
	defer conn.Close()

	counter, err := redis.Int(conn.Do("INCR", "visits"))
	if err != nil {
		http.Error(w, "Error incrementing visitor counter", http.StatusInternalServerError)
		return
	}
	fmt.Fprintf(w, "Visitor number: %d", counter)
}

func main() {
	redisHost := os.Getenv("REDISHOST")
	redisPort := os.Getenv("REDISPORT")
	redisAddr := fmt.Sprintf("%s:%s", redisHost, redisPort)

	const maxConnections = 10
	redisPool = &redis.Pool{
		MaxIdle: maxConnections,
		Dial:    func() (redis.Conn, error) { return redis.Dial("tcp", redisAddr) },
	}

	http.HandleFunc("/", incrementHandler)

	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
	}
	log.Printf("Listening on port %s", port)
	if err := http.ListenAndServe(":"+port, nil); err != nil {
		log.Fatal(err)
	}
}

자바

Jedis 라이브러리를 사용하는 경우 JedisPool을 만든 후 이 풀을 사용하여 클라이언트를 만드는 것이 좋습니다. 다음 코드 줄은 앞에서 정의한 redis.hostredis.port 환경 변수를 사용하여 풀을 만듭니다.


package com.example.redis;

import java.io.IOException;
import java.util.Properties;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

@WebListener
public class AppServletContextListener implements ServletContextListener {

  private Properties config = new Properties();

  private JedisPool createJedisPool() throws IOException {
    String host;
    Integer port;
    config.load(
        Thread.currentThread()
            .getContextClassLoader()
            .getResourceAsStream("application.properties"));
    host = config.getProperty("redis.host");
    port = Integer.valueOf(config.getProperty("redis.port", "6379"));

    JedisPoolConfig poolConfig = new JedisPoolConfig();
    // Default : 8, consider how many concurrent connections into Redis you will need under load
    poolConfig.setMaxTotal(128);

    return new JedisPool(poolConfig, host, port);
  }

  @Override
  public void contextDestroyed(ServletContextEvent event) {
    JedisPool jedisPool = (JedisPool) event.getServletContext().getAttribute("jedisPool");
    if (jedisPool != null) {
      jedisPool.destroy();
      event.getServletContext().setAttribute("jedisPool", null);
    }
  }

  // Run this before web application is started
  @Override
  public void contextInitialized(ServletContextEvent event) {
    JedisPool jedisPool = (JedisPool) event.getServletContext().getAttribute("jedisPool");
    if (jedisPool == null) {
      try {
        jedisPool = createJedisPool();
        event.getServletContext().setAttribute("jedisPool", jedisPool);
      } catch (IOException e) {
        // handle exception
      }
    }
  }
}

풀에서 클라이언트를 만들려면 JedisPool.getResource() 메서드를 사용합니다. 예를 들면 다음과 같습니다.


package com.example.redis;

import java.io.IOException;
import java.net.SocketException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

@WebServlet(name = "Track visits", value = "")
public class VisitCounterServlet extends HttpServlet {

  @Override
  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    try {
      JedisPool jedisPool = (JedisPool) req.getServletContext().getAttribute("jedisPool");

      if (jedisPool == null) {
        throw new SocketException("Error connecting to Jedis pool");
      }
      Long visits;

      try (Jedis jedis = jedisPool.getResource()) {
        visits = jedis.incr("visits");
      }

      resp.setStatus(HttpServletResponse.SC_OK);
      resp.getWriter().println("Visitor counter: " + String.valueOf(visits));
    } catch (Exception e) {
      resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
    }
  }
}

Node.js

REDISHOSTREDISPORT 환경 변수를 정의한 후 다음 줄을 사용하여 node_redis 라이브러리를 가져오고 Redis 클라이언트를 만들 수 있습니다.

'use strict';
const http = require('http');
const redis = require('redis');

const REDISHOST = process.env.REDISHOST || 'localhost';
const REDISPORT = process.env.REDISPORT || 6379;

const client = redis.createClient(REDISPORT, REDISHOST);
client.on('error', err => console.error('ERR:REDIS:', err));

// create a server
http
  .createServer((req, res) => {
    // increment the visit counter
    client.incr('visits', (err, reply) => {
      if (err) {
        console.log(err);
        res.status(500).send(err.message);
        return;
      }
      res.writeHead(200, {'Content-Type': 'text/plain'});
      res.end(`Visitor number: ${reply}\n`);
    });
  })
  .listen(8080);

PHP

REDIS_HOSTREDIS_PORT 환경 변수를 정의한 후 다음 줄을 사용하여 Redis 클라이언트를 만들 수 있습니다.

<?php
/**
 * Copyright 2019 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Only serve traffic from "/"
switch (@parse_url($_SERVER['REQUEST_URI'])['path']) {
    case '/':
        break;
    default:
        http_response_code(404);
        exit('Not Found');
}

// Connect to Memorystore from App Engine.
if (!$host = getenv('REDIS_HOST')) {
    throw new Exception('The REDIS_HOST environment variable is required');
}

# Memorystore Redis port defaults to 6379
$port = getenv('REDIS_PORT') ?: '6379';

try {
    $redis = new Redis();
    $redis->connect($host, $port);
} catch (Exception $e) {
    return print('Error: ' . $e->getMessage());
}

$value = $redis->incr('counter');

printf('Visitor number: %s', $value);

Python

REDISHOSTREDISPORT 환경 변수를 정의한 후 다음 줄을 사용하여 redis-py 라이브러리를 가져오고 클라이언트를 만듭니다.

  import redis

  redis_host = os.environ.get('REDISHOST', 'localhost')
  redis_port = int(os.environ.get('REDISPORT', 6379))
  redis_client = redis.Redis(host=redis_host, port=redis_port)

다른 앱에 이전 버전의 redis-py를 사용했다면 Client 대신 StrictClient 클래스를 사용한 것일 수 있습니다. 하지만 현재 redis-py에서는 StrictClient 대신에 Client를 사용하는 것이 좋습니다.

Ruby

이 런타임에 대한 추가 정보는 없습니다.

Redis 명령어를 사용하여 캐시에 데이터 저장 및 검색

Memorystore Redis 데이터베이스는 대부분의 Redis 명령어를 지원하지만 그 중 몇 가지 명령어만 사용하여 캐시에서 데이터를 저장 및 검색해야 합니다. 다음 표에는 데이터를 캐시하는 데 사용할 수 있는 Redis 명령어가 나와 있습니다. 앱에서 이러한 명령어를 호출하는 방법은 클라이언트 라이브러리의 문서를 참조하세요.

작업 Redis 명령어
데이터 캐시에 항목을 만들고
항목의 만료 시간 설정
SETNX
MSETNX
캐시에서 데이터 검색 GET
MGET
기존 캐시 값 바꾸기 SET
MSET
숫자 캐시 값 증가 또는 감소 INCR
INCRBY
DECR
DECRBY
캐시에서 항목 삭제 DEL
UNLINK
캐시와 동시 상호작용 지원 Redis 트랜잭션에 대한 세부정보를 참조하세요.

Python의 경우 redis-py 클라이언트 라이브러리를 사용하려면 모든 트랜잭션이 파이프라인에서 발생해야 합니다.

업데이트 테스트

앱을 로컬에서 테스트할 때는 프로덕션 데이터와의 상호작용을 방지하기 위해 Redis의 로컬 인스턴스를 실행하는 것이 좋습니다(Memorystore는 에뮬레이터를 제공하지 않음). Redis를 로컬에 설치 및 실행하려면 Redis 문서의 안내를 따르세요. 현재 Windows에서는 Redis를 로컬에서 실행할 수 없습니다.

앱 테스트에 대한 자세한 내용은 애플리케이션 테스트 및 배포를 참조하세요.

앱 배포

앱이 로컬 개발 서버에서 오류 없이 실행되면 다음을 수행합니다.

  1. App Engine에서 앱을 테스트합니다.

  2. 앱이 오류 없이 실행되면 트래픽 분할을 사용하여 업데이트된 앱의 트래픽을 천천히 늘립니다. 앱을 면밀히 모니터링하여 데이터베이스에 문제가 없는 것을 확인한 후 더 많은 트래픽을 업데이트된 앱으로 라우팅합니다.