Осторожно, закэшировано!

Сделать мое приложение на порядок быстрее потратив на это пару часов.

Пережить нагрузку в
«черную пятницу».

Что кэшировать

Статика (HTTP запросы)

Получение данных

Результаты вычислений

Шаблоны

Показать список сортов кофе

var app = express();

app.get('/', (req, res) => {
    fetch()


});


fetch()

[
    {
        name: 'Timor',
        region: 'Indonesia'
    },
    {
        name: 'Mocha',
        region: 'Yemen'
    },
    {
        name: 'Java',
        region: 'Indonesia'
    }
]
                        
var app = express();

app.get('/', (req, res) => {
    fetch()
        .then(calculate)

});


fetch() calculate()

[
    {
        name: 'Timor',
        region: 'Indonesia'
    },
    {
        name: 'Mocha',
        region: 'Yemen'
    },
    {
        name: 'Java',
        region: 'Indonesia'
    }
]
                        

{
    Indonesia: [
        'Timor',
        'Java'
    ],
    Mocha: [
        'Yemen'
    ]
}
                        
var app = express();

app.get('/', (req, res) => {
    fetch()
        .then(calculate)
        .then(data => res.json(data));
});

app.listen(3000);
without cache
without cache
without cache
without cache

Кэширование данных

var cache;

app.get('/', (req, res) => {
    if (cache) { return res.json(cache); }

    fetch()
        .then(calculate)
        .then(data => cache = data)
        .then(data => res.json(data));
});
Simple cache

Уменьшили время ответа

Cache without batching

Объединение запросов

var cache;

app.get('/', (req, res) => {
    cache = cache ||
            fetch().then(calculate);
    cache
        .then(data => res.json(data))
        .catch(() => cache = null);
});
Cache with batching

Уменьшили время ответа

Уменьшили нагрузку

lru-cache


var LRU = require('lru-cache');
var cache = LRU({ maxAge: 3000 });
        
app.get('/', (req, res) => {
    if (!cache.has('coffee')) {
        var data = fetch().then(calculate);
        cache.set('coffee', data);
    }
        
    cache.get('coffee')
        .then(data => res.json(data))
        .catch(() => cache.del('coffee'));
});

lru-cache

Уменьшили время ответа

Уменьшили нагрузку

Контролируем время жизни

Single cache
Multiple cache
Union cache

Распределенный кэш

redisio

var Redis = require('ioredis');
var cache = new Redis();
        
cache.get('coffee')
    .then(data => {
        if (data) { return data; }
        
        return fetch().then(calculate)
            .then(data => {
                cache.set('coffee', data, 'EX', 3);
                return data;
            });
    })
    .then(data => res.json(data));

Уменьшили время ответа

Уменьшили нагрузку

Контролируем время жизни

Общий кэш для всех инстансов

Кэширование шаблонов

1. Прочитать шаблон

2. Скомпилировать

3. Применить


var _ = require('lodash');
var str = '<%= date %>: "<%= error.message %>"';

module.exports = _.template(str);
        
var template = require('./template');
 
app.get('/', (req, res) => {
    var myError = Error(':-(');
 
    console.log(template({
        date: new Date(),
        error: myError
    }));
});

// Tue Jun 21 2016 13:48:02 GMT+0500 (YEKT): ":-("
Single cache

{{# cache 'menu'}}
    <h1>Coffee</h1>
    <ul>
        {{#each items}}
        <li>{{name}}</li>
        {{/each}}
    </ul>
{{/cache}}
        

Handlebars.registerHelper('cache', (key, options) => {
    if (!cache.has(key)) {
        cache.set(key, options.fn(options.data.root));
    }

    return cache.get(key);
});
        
Single cache

Эффективность кэша

«... кажется, эти данные нам нужны всегда и обновляются они редко ...»

Эффективность кэша

HitRate = 'из кэша' / 'число запросов'
Запрос Кэш Эффективность
/ 'coffee' ~99%
/:region 'Indonesia', 'Africa', ... ~98%
/:uid 'user1', 'user2', ..., 'user100500' ~1%

Кэшировать популярные данные

Не кэшировать песональные данные

Не кэшировать сложные комбинации

Разогрев кэша

Горячий старт


// ...
app.listen(3000);

var data = fetch().then(calculate);
cache.set('coffee', data);
        

SPA

without cache

<script>
    var data = {
        '/coffee': { ... },
        '/favorite': { ... },
        '/userData': { ... }
    };
</script>
        
function request(url) {
    return data[url]
      ? Promise.resolve(data[url])
      : $.get(url);
}
without cache

Кэш - вспомогательная компонента

function memoize(key, maxAge, fn) {
    if (cache.has(key)) {
        return Promise.resolve(cache.get(key));
    }
 
    return Promise.resolve()
        .then(fn)
        .then(results => {
            cache.set(key, result, maxAge);
            return result;
        });
}
Models abstraction

Легко менять реализацию кэша

Настрока кэширования моделей

Кэширование на клиенте

cookie

sessionStorage

localStorage


function setItem(key, maxAge, value) {
    var data = JSON.stringify({
        expire: Date.now() + maxAge,
        value: value
    });

    localStorage.setItem(key, data);
}
        

function getItem(key, maxAge, value) {
    var store = localStorage.getItem(key);
    var data = JSON.parse(store);

    if(data.expire > Date.now()) {
        return data.value;
    }
}
        
cache.set('speaker', {
    name: 'Жигалов Сергей',
    twitter: '@sergey_zhigalov',
    email: 'zhigalov@yandex-team.ru'
});
cache.set('assistant', {
    name: 'Мокеев Евгений'
});

Спасибо!