Oral  Brekke

Oral Brekke

1670971020

How to Test ETag Browser Caching with cURL Requests

Recently I’ve been playing around with Netlify and as a result I’m becoming more familiar with caching strategies commonly found with content delivery networks (CDN). One such strategy makes use of ETag identifiers for web resources.

In short, an ETag identifier is a value, typically a hash, that represents the version of a particular web resource. The resource is cached within the browser along with the ETag value and that value is used when determining if the particular cached resource has changed remotely.

We’re going to explore how to simulate the requests that the browser makes when working with ETag identifiers, but using simple cURL requests instead.

To get started, we’re going to make a request for a resource:

$ curl -I https://www.thepolyglotdeveloper.com/css/custom.min.css

HTTP/2 200 
accept-ranges: bytes
cache-control: public, max-age=0, must-revalidate
content-length: 7328
content-type: text/css; charset=UTF-8
date: Wed, 04 Sep 2019 00:41:04 GMT
strict-transport-security: max-age=31536000
etag: "018b8b0ecb632aab770af328f043b119-ssl"
age: 0
server: Netlify
x-nf-request-id: 65a8e1aa-03a0-4b6c-9f46-51aba795ad83-921013

In the above request I’ve only requested the header information from the response. For this tutorial, the body of the response isn’t important to us.

Take note of the cache-control and the etag headers as well as the response code.

In the scenario of Netlify, the cache-control header tells the browser to cache the resource, but also not to trust the cache. This is done so the client always attempts to get the latest resource. The etag header represents the version of the resource and it is sent with future requests. If the server says the etag hasn’t changed between requests then the response will have a 304 code and the cached resource will be used instead.

So let’s check to see if the resource has changed with cURL:

$ curl -I -H 'If-None-Match: "018b8b0ecb632aab770af328f043b119-ssl"' https://www.thepolyglotdeveloper.com/css/custom.min.css

HTTP/2 304 
date: Wed, 04 Sep 2019 00:53:24 GMT
etag: "018b8b0ecb632aab770af328f043b119-ssl"
cache-control: public, max-age=0, must-revalidate
server: Netlify
x-nf-request-id: eca29310-c9bf-4742-87e1-3412e8852381-2165939

With the new request to the same resource, the If-None-Match header is included and the value is the etag hash from the previous request.

Notice that this time around the response status code was 304 as anticipated. Had the etag been different, a 200 response would have happened with a new etag hash.

Interacting with Compressed Cached Resources

If you look at your browser’s network inspector you might notice that etag hashes for resources have a -df value appended to them. For example, for the same resource, my browser is showing the following:

018b8b0ecb632aab770af328f043b119-ssl-df

While similar, it isn’t totally the same as the etag hash that came back with the previous cURL requests. Try to run a cURL request with the above etag value:

$ curl -I -H 'If-None-Match: "018b8b0ecb632aab770af328f043b119-ssl-df"' https://www.thepolyglotdeveloper.com/css/custom.min.css

HTTP/2 200 
accept-ranges: bytes
cache-control: public, max-age=0, must-revalidate
content-length: 7328
content-type: text/css; charset=UTF-8
date: Wed, 04 Sep 2019 01:03:13 GMT
strict-transport-security: max-age=31536000
etag: "018b8b0ecb632aab770af328f043b119-ssl"
age: 0
server: Netlify
x-nf-request-id: 2734ffab-c611-4fc9-841e-460f172aa3b4-1604468

The response was not a 304 code because the -df means that it is a compressed version of the URL. As it stands, our cURL requests have been for uncompressed versions of the URL.

A Support Engineer at Netlify pointed this difference out to me in this forum thread.

In most circumstances the web browser will include the appropriate header information to work with compressed resources, so in cURL we have to do something different.

To get beyond this with cURL, the following request would work:

$ curl --compressed -I -H 'If-None-Match: "018b8b0ecb632aab770af328f043b119-ssl-df"' https://www.thepolyglotdeveloper.com/css/custom.min.css

HTTP/2 304 
date: Wed, 04 Sep 2019 01:07:36 GMT
etag: "018b8b0ecb632aab770af328f043b119-ssl-df"
cache-control: public, max-age=0, must-revalidate
server: Netlify
vary: Accept-Encoding
x-nf-request-id: 65a8e1aa-03a0-4b6c-9f46-51aba795ad83-1301670

Notice in the above request that we’re now using the --compressed flag with cURL. As a result, we get a 304 response indicating that the resource hasn’t changed and we should used the locally cached copy.

Alternatively, we could execute the following cURL request:

$ curl -I -H 'If-None-Match: "018b8b0ecb632aab770af328f043b119-ssl-df"' -H 'Accept-Encoding: gzip, deflate, br' https://www.thepolyglotdeveloper.com/css/custom.min.css

HTTP/2 304 
date: Wed, 04 Sep 2019 01:12:34 GMT
etag: "018b8b0ecb632aab770af328f043b119-ssl-df"
cache-control: public, max-age=0, must-revalidate
server: Netlify
vary: Accept-Encoding
x-nf-request-id: eca29310-c9bf-4742-87e1-3412e8852381-2432816

Instead of using the --compressed flag, we are including an accept-encoding header.

Again, information around compressed versions were provided to me by Luke Lawson from Netlify in this forum thread.

Conclusion

You just saw how to simulate the same caching that happens in the web browser using cURL instead. Since I’m new to content delivery networks (CDN) and how they handle caching, this was very useful to me when it came to testing how caching worked with the etag hash for any given resource. The 304 response will always be received quicker and with a smaller payload than a 200 response which saves bandwidth and performance without sacrificing the freshness of content.

In theory, the CDN will maintain versioning information for a given resource and as a result will be able to validate etag values for freshness. It is not up to the browser to determine if the etag is stale.

Original article source at: https://www.thepolyglotdeveloper.com/

#caching #test #requests

How to Test ETag Browser Caching with cURL Requests

How to Serve Map Clusters 50x Faster Using Smarter Caching

Serving map clusters to a mobile app can cause a significant performance bottleneck. Fortunately, it’s a problem that can be solved with this caching strategy.

Map components that feature location markers are common in mobile apps today. For example, the Airbnb app prominently features such markers, fetched from a web service, to represent available properties on a map.

To ensure that the volume of fetched data does not become unmanageable as the number of markers grows, the server groups those markers together before sending them to the client. A map cluster is a specialized marker whose position equals the average position of the markers it subsumes. It is labeled with the number of markers it represents.

Still, serving clusters can create performance bottlenecks because a web service must retrieve every single marker within a given geographical region from the database. Fortunately, this issue can be resolved with a caching strategy. To better understand how to design and maintain a cache, let’s look at an example map API endpoint I built for the Playsports app.

A Scaling Problem and a Naive Solution

In the Playsports map, each marker represents a sports facility. The map’s API endpoint needs to return a list of markers and marker clusters, given a zoom level and a bounding box.

 

A 2D world map with several thumbtacks, several circles with numbers in them, and a square, dotted boundary encompassing Europe and half of Africa.

A bounding box, markers, and clusters on a map

 

If the number of markers is small enough, we can load all markers in the bounding box from the database, cluster as necessary, and return the resultant markers and clusters to the client.

At the outset, the maximum number of markers in any reachable bounding box in Playsports was ~400, resulting in an endpoint speed of ~0.5 seconds. Implementing the naive solution was sufficient for this use case.

As the number of markers grew, however, the mechanism’s inefficiency became obvious. After we added ~10,000 new sports facility markers, the endpoint speed slowed to ~4.3 seconds under some zoom levels. This rate is far below the one-second duration that is generally considered to be the maximum acceptable delay for a user action in a mobile app.

To better understand the naive solution’s inefficiencies, let’s analyze it in the context of the marker query:

  1. From the database, retrieve all markers in the bounding box. For most apps, this step needs to include fetching marker details beyond latitude/longitude positioning. For example, Airbnb’s markers include the price, whether the user has already viewed the property, and whether they have marked it as a favorite.
  2. On the retrieved markers, run a map-clustering algorithm that incorporates the zoom level.
  3. Return clusters and (detailed) markers to the client.

As the number of markers increases, performance deteriorates in Steps 1 and 2:

  • When the bounding box is large enough, and when more than 10,000 markers require a detail lookup via a JOIN, the database query slows down dramatically (by 1 to 2 seconds).
  • Feeding 10,000 items to the map-clustering algorithm takes another ~250 milliseconds.

Assuming a constant window size, when a bounding box is relatively large, the zoom level is usually low (i.e., zoomed out far). Under low zoom levels, results tend to favor clusters to avoid visual crowding. Thus, the greatest potential for optimization lies in the first step of the solution, where thousands of individual markers are loaded. We don’t need most of these markers in the result. We only need each of them to count toward a cluster.

Architecting the Optimized Solution

The naive solution takes significantly longer to complete a worst-case query: a maximum bounding box in a marker-dense region. In contrast, the optimized solution would take just 73 ms, representing a ~58x speedup. From a high level, it looks like this:

  1. The Caching Strategy. Retrieve markers and clusters in a bounding box from a zoom level-specific cache.
  2. Additional Marker Detail (Optional). Enhance markers with a payload fetched from the database.
  3. Returning the Result. Return markers and clusters to the client.

The main complexity lies in the architecture of the cache (i.e., the first step).

Step 1: The Caching Strategy

This main step consists of six components:

Technology

Redis is a fast, in-memory database that is commonly used as a cache. Its built-in geospatial indexing uses the Geohash algorithm for the efficient storage and retrieval of latitude-longitude points, which is precisely what we need for our markers.

A Cache for Each Zoom Level

The degree of map clustering (whether single markers or clusters are returned) is determined by the zoom level passed to the endpoint.

  • For high zoom levels (i.e., zoomed in close), the result will favor individual markers, except in marker-dense regions.
  • For low zoom levels (i.e., zoomed out far), the result will favor clusters, except in marker-sparse regions.

Google Maps supports zoom levels from zero to a maximum of 20, depending on the area. (Ranges supported by other map services are similar. For instance, Mapbox uses zoom levels from zero to a maximum of 23.) Since these zoom levels are also integer values, we can simply create a separate cache for each zoom level.

To support all zoom levels in Google Maps—except level zero, which is too far zoomed out—we will create 20 distinct caches. Each cache will store all markers and clusters for the entire map, as clustered for the zoom level it represents.

 

A 2D world map with one numbered circle in North America, one in Asia, and one in Africa. At right is an indicator that this cache is for Zoom Level 1. A second 2D world map is peppered with dozens of thumbtacks. At right is an indicator that this cache is for Zoom Level 20.

Comparison of two zoom-level views

 

Cache Data Types

Each cache would store clusters and individual markers. For either type of entry, we’ll need to populate several fields:

Field NameNote
TypeCluster or marker
Latitude and longitudeWe duplicate Redis’ internal geospatial storage for convenience.
ID
(for markers only)
In Step 2, we can use this value to fetch details beyond location, such as user interaction history.
Quantity of subsumed markers
(for clusters only)
In Step 2, we can fetch aggregate data (e.g., the number of unviewed markers) for these too.

However, Redis allows users to store only the location, plus a single string, as the value in a geospatial set. To get around this restriction, we serialize the above fields in a JSON string. We then use this string as the value in Redis. The maximum size of both keys and values in Redis is 512 MB, which is more than enough for this use case.

Filling the Caches

To fill the caches, we retrieve all of the markers from the database. For each zoom level, we execute the map-clustering algorithm. We use Redis’ GEOADD to insert the resulting markers and clusters into the cache of the corresponding zoom level, and pass along the latitude and longitude, plus the previously described JSON string.

Running the map-clustering algorithm on the whole map at this stage (rather than on a bounding box from the user, on demand) might theoretically produce some edge differences in cluster placement, but the overall user experience will remain the same.

Querying the Cache

For an incoming request, we pass the given bounding box to the Redis GEOSEARCH command, which queries the cache of the given zoom level to retrieve marker and cluster candidates in the area.

Designing a Cache Refresh Plan

A 20-level cache refresh is expensive, so it’s best to refresh as infrequently as your project requirements allow. For example, the addition or removal of a sports facility in Playsports only marks the cache as stale; an hourly cron job then refreshes the cache, if needed. More on this in the Cache Staleness section.

Step 2: Additional Marker Detail (Optional)

At this point, most apps will need to fetch details based on individual marker IDs. We could make this detail part of the stringified JSON values in the cache, but in many apps, marker details are user-specific. Since there’s a single, shared cache for all users, it’s not possible to store these additional fields there.

Our optimized solution takes the IDs of the individual markers from the cache result and fetches their details from the database. Now we’re only looking up the individual markers that are left after clustering. There won’t be too many of these when the map is zoomed out (because we’ll have mostly clusters) nor when zoomed in (because the area will be small).

In contrast, the naive solution looks up all markers in the bounding box (and their details) before clustering. Thus, this step—optional, but for many apps, crucial—now runs much faster.

Step 3: Returning the Result

With the clusters built and the individual markers enhanced, we can now deliver these to the client. Aside from some inconsequential edge cases, the resulting data is nearly identical to the naive solution—but we’re able to deliver it much faster.

Further Considerations

Now let’s look at two additional factors:

Resource Needs

Let’s assume that an app’s map contains N markers total. As there are up to 20 zoom levels, we would need to store, at most, 20N cache items. In practice, however, the actual number of cache items is often much lower due to clustering, especially in the lower zoom levels. In fact, the total number of cache items distributed among all of Playsports’ caches is only ~2N.

Further, if we assume that the length of a cache value (the stringified JSON) is ~250 characters (a reasonable assumption, at least for Playsports) and the string size is 1 byte per character, then the amount of cache storage needed for the JSON values would be approximately 2 * N * 250 bytes.

To this figure we add Redis’ internal data structures for the sorted sets used by GEOADD. Redis uses 85 MB of memory for 1 million small key-value pairs, so we can assume Redis internals account for less than 100 bytes per cache item. That means we could use a 1 GB-RAM Redis instance to support as many as 1.4 million markers. Even in the unlikely scenario of the markers being evenly spread out across the entire map, resulting in the number of cache items nearing 20N, N could still go up to ~140,000. Since a Redis instance can handle more than 4 billion keys (232, to be exact), this is not a limiting factor.

Cache Staleness

Depending on the use case, it may not be sufficient to refresh the cache only periodically. In such cases, we could use a rate-limited task queue. It would reduce cache staleness, while still limiting the number of cache refreshes, and thus the load on the system.

After each database update, we would queue a cache refresh job. This queue would limit the number of tasks to M per hour. This compromise allows for faster-than-hourly updates while maintaining a relatively low load on the system (depending on M).

Caching Strategies Outweigh Map-clustering Algorithms

The optimized solution for Playsports—more than 50 times faster than the naive solution—has worked well. It should continue to work well, until we reach 1.4 million markers or more than 100 times the existing data.

For most map-based web service requests, some form of precalculation is needed to allow for scalability. The type of technology to be used, as well as the specific design, would depend on business requirements. Cache staleness needs, marker augmentation, and the number of markers are important characteristics to take into account when designing a solution.

Original article source at: https://www.toptal.com/

#caching #map #cluster 

How to Serve Map Clusters 50x Faster Using Smarter Caching
Nigel  Uys

Nigel Uys

1668995760

How to Detailed look at Django's built-in caching options

Caching is typically the most effective way to boost an application's performance.

For dynamic websites, when rendering a template, you'll often have to gather data from various sources (like a database, the file system, and third-party APIs, to name a few), process the data, and apply business logic to it before serving it up to a client. Any delay due to network latency will be noticed by the end user.

For instance, say you have to make an HTTP call to an external API to grab the data required to render a template. Even in perfect conditions this will increase the rendering time which will increase the overall load time. What if the API goes down or maybe you're subject to rate limiting? Either way, if the data is infrequently updated, it's a good idea to implement a caching mechanism to prevent having to make the HTTP call altogether for each client request.

This article looks at how to do just that by first reviewing Django's caching framework as a whole and then detailing step-by-step how to cache a Django view.

Dependencies:

  1. Django v3.2.5
  2. django-redis v5.0.0
  3. Python v3.9.6
  4. pymemcache v3.5.0
  5. Requests v2.26.0

--

Django Caching Articles:

  1. Caching in Django (this article!)
  2. Low-Level Cache API in Django

Objectives

By the end of this tutorial, you should be able to:

  1. Explain why you may want to consider caching a Django view
  2. Describe Django's built-in options available for caching
  3. Cache a Django view with Redis
  4. Load test a Django app with Apache Bench
  5. Cache a Django view with Memcached

Django Caching Types

Django comes with several built-in caching backends, as well as support for a custom backend.

The built-in options are:

  1. Memcached: Memcached is a memory-based, key-value store for small chunks of data. It supports distributed caching across multiple servers.
  2. Database: Here, the cache fragments are stored in a database. A table for that purpose can be created with one of the Django's admin commands. This isn't the most performant caching type, but it can be useful for storing complex database queries.
  3. File system: The cache is saved on the file system, in separate files for each cache value. This is the slowest of all the caching types, but it's the easiest to set up in a production environment.
  4. Local memory: Local memory cache, which is best-suited for your local development or testing environments. While it's almost as fast as Memcached, it cannot scale beyond a single server, so it's not appropriate to use as a data cache for any app that uses more than one web server.
  5. Dummy: A "dummy" cache that doesn't actually cache anything but still implements the cache interface. It's meant to be used in development or testing when you don't want caching, but do not wish to change your code.

Django Caching Levels

Caching in Django can be implemented on different levels (or parts of the site). You can cache the entire site or specific parts with various levels of granularity (listed in descending order of granularity):

Per-site cache

This is the easiest way to implement caching in Django. To do this, all you'll have to do is add two middleware classes to your settings.py file:

MIDDLEWARE = [
    'django.middleware.cache.UpdateCacheMiddleware',     # NEW
    'django.middleware.common.CommonMiddleware',
    'django.middleware.cache.FetchFromCacheMiddleware',  # NEW
]

The order of the middleware is important here. UpdateCacheMiddleware must come before FetchFromCacheMiddleware. For more information take a look at Order of MIDDLEWARE from the Django docs.

You then need to add the following settings:

CACHE_MIDDLEWARE_ALIAS = 'default'  # which cache alias to use
CACHE_MIDDLEWARE_SECONDS = '600'    # number of seconds to cache a page for (TTL)
CACHE_MIDDLEWARE_KEY_PREFIX = ''    # should be used if the cache is shared across multiple sites that use the same Django instance

Although caching the entire site could be a good option if your site has little or no dynamic content, it may not be appropriate to use for large sites with a memory-based cache backend since RAM is, well, expensive.

Per-view cache

Rather than wasting precious memory space on caching static pages or dynamic pages that source data from a rapidly changing API, you can cache specific views. This is the approach that we'll use in this article. It's also the caching level that you should almost always start with when looking to implement caching in your Django app.

You can implement this type of cache with the cache_page decorator either on the view function directly or in the path within URLConf:

from django.views.decorators.cache import cache_page

@cache_page(60 * 15)
def your_view(request):
    ...

# or

from django.views.decorators.cache import cache_page

urlpatterns = [
    path('object/<int:object_id>/', cache_page(60 * 15)(your_view)),
]

The cache itself is based on the URL, so requests to, say, object/1 and object/2 will be cached separately.

It's worth noting that implementing the cache directly on the view makes it more difficult to disable the cache in certain situations. For example, what if you wanted to allow certain users access to the view without the cache? Enabling the cache via the URLConf provides the opportunity to associate a different URL to the view that doesn't use the cache:

from django.views.decorators.cache import cache_page

urlpatterns = [
    path('object/<int:object_id>/', your_view),
    path('object/cache/<int:object_id>/', cache_page(60 * 15)(your_view)),
]

Template fragment cache

If your templates contain parts that change often based on the data you'll probably want to leave them out of the cache.

For example, perhaps you use the authenticated user's email in the navigation bar in an area of the template. Well, If you have thousands of users then that fragment will be duplicated thousands of times in RAM, one for each user. This is where template fragment caching comes into play, which allows you to specify the specific areas of a template to cache.

To cache a list of objects:

{% load cache %}

{% cache 500 object_list %}
  <ul>
    {% for object in objects %}
      <li>{{ object.title }}</li>
    {% endfor %}
  </ul>
{% endcache %}

Here, {% load cache %} gives us access to the cache template tag, which expects a cache timeout in seconds (500) along with the name of the cache fragment (object_list).

Low-level cache API

For cases where the previous options don't provide enough granularity, you can use the low-level API to manage individual objects in the cache by cache key.

For example:

from django.core.cache import cache


def get_context_data(self, **kwargs):
    context = super().get_context_data(**kwargs)

    objects = cache.get('objects')

    if objects is None:
        objects = Objects.all()
        cache.set('objects', objects)

    context['objects'] = objects

    return context

In this example, you'll want to invalidate (or remove) the cache when objects are added, changed, or removed from the database. One way to manage this is via database signals:

from django.core.cache import cache
from django.db.models.signals import post_delete, post_save
from django.dispatch import receiver


@receiver(post_delete, sender=Object)
def object_post_delete_handler(sender, **kwargs):
     cache.delete('objects')


@receiver(post_save, sender=Object)
def object_post_save_handler(sender, **kwargs):
    cache.delete('objects')

For more on using database signals to invalidate cache, check out the Low-Level Cache API in Django article.

With that, let's look at some examples.

Project Setup

Clone down the base project from the cache-django-view repo, and then check out the base branch:

$ git clone https://github.com/testdrivenio/cache-django-view.git --branch base --single-branch
$ cd cache-django-view

Create (and activate) a virtual environment and install the requirements:

$ python3.9 -m venv venv
$ source venv/bin/activate
(venv)$ pip install -r requirements.txt

Apply the Django migrations, and then start the server:

(venv)$ python manage.py migrate
(venv)$ python manage.py runserver

Navigate to http://127.0.0.1:8000 in your browser of choice to ensure that everything works as expected.

You should see:

uncached webpage

Take note of your terminal. You should see the total execution time for the request:

Total time: 2.23s

This metric comes from core/middleware.py:

import logging
import time


def metric_middleware(get_response):
    def middleware(request):
        # Get beginning stats
        start_time = time.perf_counter()

        # Process the request
        response = get_response(request)

        # Get ending stats
        end_time = time.perf_counter()

        # Calculate stats
        total_time = end_time - start_time

        # Log the results
        logger = logging.getLogger('debug')
        logger.info(f'Total time: {(total_time):.2f}s')
        print(f'Total time: {(total_time):.2f}s')

        return response

    return middleware

Take a quick look at the view in apicalls/views.py:

import datetime

import requests
from django.views.generic import TemplateView


BASE_URL = 'https://httpbin.org/'


class ApiCalls(TemplateView):
    template_name = 'apicalls/home.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        response = requests.get(f'{BASE_URL}/delay/2')
        response.raise_for_status()
        context['content'] = 'Results received!'
        context['current_time'] = datetime.datetime.now()
        return context

This view makes an HTTP call with requests to httpbin.org. To simulate a long request, the response from the API is delayed for two seconds. So, it should take about two seconds for http://127.0.0.1:8000 to render not only on the initial request, but for each subsequent request as well. While a two second load is somewhat acceptable on the initial request, it's completely unacceptable for subsequent requests since the data is not changing. Let's fix this by caching the entire view using Django's Per-view cache level.

Workflow:

  1. Make full HTTP call to httpbin.org on the initial request
  2. Cache the view
  3. Subsequent requests will then pull from the cache, bypassing the HTTP call
  4. Invalidate the cache after a period of time (TTL)

Baseline Performance Benchmark

Before adding cache, let's quickly run a load test to get a benchmark baseline using Apache Bench, to get rough sense of how many requests our application can handle per second.

Apache Bench comes pre-installed on Mac.

If you're on a Linux system, chances are it's already installed and ready to go as well. If not, you can install via APT (apt-get install apache2-utils) or YUM (yum install httpd-tools).

Windows users will need to download and extract the Apache binaries.

Add Gunicorn to the requirements file:

gunicorn==20.1.0

Kill the Django dev server and install Gunicorn:

(venv)$ pip install -r requirements.txt

Next, serve up the Django app with Gunicorn (and four workers) like so:

(venv)$ gunicorn core.wsgi:application -w 4

In a new terminal window, run Apache Bench:

$ ab -n 100 -c 10 http://127.0.0.1:8000/

This will simulate 100 connections over 10 concurrent threads. That's 100 requests, 10 at a time.

Take note of the requests per second:

Requests per second:    1.69 [#/sec] (mean)

Keep in mind that Django Debug Toolbar will add a bit of overhead. Benchmarking in general is difficult to get perfectly right. The important thing is consistency. Pick a metric to focus on and use the same environment for each test.

Kill the Gunicorn server and spin the Django dev server back up:

(venv)$ python manage.py runserver

With that, let's look at how to cache a view.

Caching a View

Start by decorating the ApiCalls view with the @cache_page decorator like so:

import datetime

import requests
from django.utils.decorators import method_decorator # NEW
from django.views.decorators.cache import cache_page # NEW
from django.views.generic import TemplateView


BASE_URL = 'https://httpbin.org/'


@method_decorator(cache_page(60 * 5), name='dispatch') # NEW
class ApiCalls(TemplateView):
    template_name = 'apicalls/home.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        response = requests.get(f'{BASE_URL}/delay/2')
        response.raise_for_status()
        context['content'] = 'Results received!'
        context['current_time'] = datetime.datetime.now()
        return context

Since we're using a class-based view, we can't put the decorator directly on the class, so we used a method_decorator and specified dispatch (as the method to be decorated) for the name argument.

The cache in this example sets a timeout (or TTL) of five minutes.

Alternatively, you could set this in your settings like so:

# Cache time to live is 5 minutes
CACHE_TTL = 60 * 5

Then, back in the view:

import datetime

import requests
from django.conf import settings
from django.core.cache.backends.base import DEFAULT_TIMEOUT
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page
from django.views.generic import TemplateView

BASE_URL = 'https://httpbin.org/'
CACHE_TTL = getattr(settings, 'CACHE_TTL', DEFAULT_TIMEOUT)


@method_decorator(cache_page(CACHE_TTL), name='dispatch')
class ApiCalls(TemplateView):
    template_name = 'apicalls/home.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        response = requests.get(f'{BASE_URL}/delay/2')
        response.raise_for_status()
        context['content'] = 'Results received!'
        context['current_time'] = datetime.datetime.now()
        return context

Next, let's add a cache backend.

Redis vs Memcached

Memcached and Redis are in-memory, key-value data stores. They are easy to use and optimized for high-performance lookups. You probably won't see much difference in performance or memory usage between the two. That said, Memcached is slightly easier to configure since it's designed for simplicity and ease of use. Redis, on the other hand, has a richer set of features so it has a wide range of use cases beyond caching. For example, it's often used to store user sessions or as message broker in a pub/sub system. Because of its flexibility, unless you're already invested in Memcached, Redis is much better solution.

For more on this, review this Stack Overflow answer.

Next, pick your data store of choice and let's look at how to cache a view.

Option 1: Redis with Django

Download and install Redis.

If you’re on a Mac, we recommend installing Redis with Homebrew:

$ brew install redis

Once installed, in a new terminal window start the Redis server and make sure that it's running on its default port, 6379. The port number will be important when we tell Django how to communicate with Redis.

$ redis-server

For Django to use Redis as a cache backend, we first need to install django-redis.

Add it to the requirements.txt file:

django-redis==5.0.0

Install:

(venv)$ pip install -r requirements.txt

Next, add the custom backend to the settings.py file:

CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': 'redis://127.0.0.1:6379/1',
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient',
        }
    }
}

Now, when you run the server again, Redis will be used as the cache backend:

(venv)$ python manage.py runserver

With the server up and running, navigate to http://127.0.0.1:8000.

The first request will still take about two seconds. Refresh the page. The page should load almost instantaneously. Take a look at the load time in your terminal. It should be close to zero:

Total time: 0.01s

Curious what the cached data looks like inside of Redis?

Run Redis CLI in interactive mode in a new terminal window:

$ redis-cli

You should see:

127.0.0.1:6379>

Run ping to ensure everything works properly:

127.0.0.1:6379> ping
PONG

Turn back to the settings file. We used Redis database number 1: 'LOCATION': 'redis://127.0.0.1:6379/1',. So, run select 1 to select that database and then run keys * to view all the keys:

127.0.0.1:6379> select 1
OK

127.0.0.1:6379[1]> keys *
1) ":1:views.decorators.cache.cache_header..17abf5259517d604cc9599a00b7385d6.en-us.UTC"
2) ":1:views.decorators.cache.cache_page..GET.17abf5259517d604cc9599a00b7385d6.d41d8cd98f00b204e9800998ecf8427e.en-us.UTC"

We can see that Django put in one header key and one cache_page key.

To view the actual cached data, run the get command with the key as an argument:

127.0.0.1:6379[1]> get ":1:views.decorators.cache.cache_page..GET.17abf5259517d604cc9599a00b7385d6.d41d8cd98f00b204e9800998ecf8427e.en-us.UTC"

Your should see something similar to:

"\x80\x05\x95D\x04\x00\x00\x00\x00\x00\x00\x8c\x18django.template.response\x94\x8c\x10TemplateResponse
\x94\x93\x94)\x81\x94}\x94(\x8c\x05using\x94N\x8c\b_headers\x94}\x94(\x8c\x0ccontent-type\x94\x8c\
x0cContent-Type\x94\x8c\x18text/html; charset=utf-8\x94\x86\x94\x8c\aexpires\x94\x8c\aExpires\x94\x8c\x1d
Fri, 01 May 2020 13:36:59 GMT\x94\x86\x94\x8c\rcache-control\x94\x8c\rCache-Control\x94\x8c\x0
bmax-age=300\x94\x86\x94u\x8c\x11_resource_closers\x94]\x94\x8c\x0e_handler_class\x94N\x8c\acookies
\x94\x8c\x0chttp.cookies\x94\x8c\x0cSimpleCookie\x94\x93\x94)\x81\x94\x8c\x06closed\x94\x89\x8c
\x0e_reason_phrase\x94N\x8c\b_charset\x94N\x8c\n_container\x94]\x94B\xaf\x02\x00\x00
<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Home</title>\n
<link rel=\"stylesheet\" href=\"https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css\
"\n          integrity=\"sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh\"
crossorigin=\"anonymous\">\n\n</head>\n<body>\n<div class=\"container\">\n    <div class=\"pt-3\">\n
<h1>Below is the result of the APICall</h1>\n    </div>\n    <div class=\"pt-3 pb-3\">\n
<a href=\"/\">\n            <button type=\"button\" class=\"btn btn-success\">\n
Get new data\n            </button>\n        </a>\n    </div>\n    Results received!<br>\n
13:31:59\n</div>\n</body>\n</html>\x94a\x8c\x0c_is_rendered\x94\x88ub."

Exit the interactive CLI once done:

127.0.0.1:6379[1]> exit

Skip down to the "Performance Tests" section.

Option 2: Memcached with Django

Start by adding pymemcache to the requirements.txt file:

pymemcache==3.5.0

Install the dependencies:

(venv)$ pip install -r requirements.txt

Next, we need to update the settings in core/settings.py to enable the Memcached backend:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
        'LOCATION': '127.0.0.1:11211',
    }
}

Here, we added the PyMemcacheCache backend and indicated that Memcached should be running on our local machine on localhost (127.0.0.1) port 11211, which is the default port for Memcached.

Next, we need to install and run the Memcached daemon. The easiest way to install it, is via a package manager like APT, YUM, Homebrew, or Chocolatey depending on your operating system:

# linux
$ apt-get install memcached
$ yum install memcached

# mac
$ brew install memcached

# windows
$ choco install memcached

Then, run it in a different terminal on port 11211:

$ memcached -p 11211

# test: telnet localhost 11211

For more information on installation and configuration of Memcached, review the official wiki.

Navigate to http://127.0.0.1:8000 in our browser again. The first request will still take the full two seconds, but all subsequent requests will take advantage of the cache. So, if you refresh or press the "Get new data button", the page should load almost instantly.

What's the execution time look like in your terminal?

Total time: 0.03s

Performance Tests

If we look at the time it takes to load the first request vs. the second (cached) request in Django Debug Toolbar, it will look similar to:

load time uncached

load time with cache

Also in the Debug Toolbar, you can see the cache operations:

cache operations

Spin Gunicorn back up again and re-run the performance tests:

$ ab -n 100 -c 10 http://127.0.0.1:8000/

What are the new requests per second? It's about 36 on my machine!

Conclusion

In this article, we looked at the different built-in options for caching in Django as well as the different levels of caching available. We also detailed how to cache a view using Django's Per-view cache with both Memcached and Redis.

You can find the final code for both options, Memcached and Redis, in the cache-django-view repo.

--

In general, you'll want to look to caching when page rendering is slow due to network latency from database queries or HTTP calls.

From there, it's highly recommend to use a custom Django cache backend with Redis with a Per-view type. If you need more granularity and control, because not all of the data on the template is the same for all users or parts of the data changes frequently, then jump down to the Template fragment cache or Low-level cache API.

--

Django Caching Articles:

  1. Caching in Django (this article!)
  2. Low-Level Cache API in Django

Original article source at: https://testdriven.io/ 

#django #caching 

How to Detailed look at Django's built-in caching options
Oral  Brekke

Oral Brekke

1668846300

How to Faster CI Builds with Docker Layer Caching and BuildKit

This article takes a look at how to speed up your Docker-based builds on CircleCI, GitLab CI, and GitHub Actions with Docker layer Caching and BuildKit.

Docker Layer Caching

Docker caches each layer as an image is built, and each layer will only be re-built if it or the layer above it has changed since the last build. So, you can significantly speed up builds with Docker cache. Let's take a look at a quick example.

Dockerfile:

# pull base image
FROM python:3.9.7-slim

# install netcat
RUN apt-get update && \
    apt-get -y install netcat && \
    apt-get clean

# set working directory
WORKDIR /usr/src/app

# install requirements
COPY ./requirements.txt .
RUN pip install -r requirements.txt

# add app
COPY . .

# run server
CMD gunicorn -b 0.0.0.0:5000 manage:app

You can find the full source code for this project in the docker-ci-cache repo on GitHub.

The first Docker build can take several minutes to complete, depending on your connection speed. Subsequent builds should only take a few seconds since the layers get cached after that first build:

[+] Building 0.4s (12/12) FINISHED
 => [internal] load build definition from Dockerfile                                                                     0.0s
 => => transferring dockerfile: 37B                                                                                      0.0s
 => [internal] load .dockerignore                                                                                        0.0s
 => => transferring context: 35B                                                                                         0.0s
 => [internal] load metadata for docker.io/library/python:3.9.7-slim                                                     0.3s
 => [internal] load build context                                                                                        0.0s
 => => transferring context: 555B                                                                                        0.0s
 => [1/7] FROM docker.io/library/python:3.9.7-slim@sha256:bdefda2b80c5b4d993ef83d2445d81b2b894bf627b62bd7b0f01244de2b6a  0.0s
 => CACHED [2/7] RUN apt-get update &&     apt-get -y install netcat &&     apt-get clean                                0.0s
 => CACHED [3/7] WORKDIR /usr/src/app                                                                                    0.0s
 => CACHED [4/7] COPY ./requirements.txt .                                                                               0.0s
 => CACHED [5/7] RUN pip install -r requirements.txt                                                                     0.0s
 => CACHED [6/7] COPY project .                                                                                          0.0s
 => CACHED [7/7] COPY manage.py .                                                                                        0.0s
 => exporting to image                                                                                                   0.0s
 => => exporting layers                                                                                                  0.0s
 => => writing image sha256:2b8b7c5a6d1b77d5bcd689ab265b0281ad531bd2e34729cff82285f5abdcb59f                             0.0s
 => => naming to docker.io/library/cache                                                                                 0.0s

Even if you make a change to the source code it should still only take a few seconds to build as the dependencies will not need to be downloaded. Only the last two layers have to be re-built, in other words:

 => [6/7] COPY project .
 => [7/7] COPY manage.py .

To avoid invalidating the cache:

  1. Start your Dockerfile with commands that are less likely to change
  2. Place commands that are more likely to change (like COPY . .) as late as possible
  3. Add only the necessary files (use a .dockerignore file)

For more tips and best practices, check out the Docker Best Practices for Python Developers article.

BuildKit

If you're using a Docker version >= 19.03 you can use BuildKit, a container image builder, in place of the traditional image builder back-end inside the Docker engine. Without BuildKit, if an image doesn't exist on your local image registry, you would need to pull the remote images before building in order to take advantage of Docker layer caching.

Example:

$ docker pull mjhea0/docker-ci-cache:latest

$ docker docker build --tag mjhea0/docker-ci-cache:latest .

With BuildKit, you don't need to pull the remote images before building since it caches each build layer in your image registry. Then, when you build the image, each layer is downloaded as needed during the build.

To enable BuildKit, set the DOCKER_BUILDKIT environment variable to 1. Then, to turn on the inline layer caching, use the BUILDKIT_INLINE_CACHE build argument.

Example:

export DOCKER_BUILDKIT=1

# Build and cache image
$ docker build --tag mjhea0/docker-ci-cache:latest --build-arg BUILDKIT_INLINE_CACHE=1 .

# Build image from remote cache
$ docker build --cache-from mjhea0/docker-ci-cache:latest .

CI Environments

Since CI platforms provide a fresh environment for every build, you'll need to use a remote image registry as the source of the cache for BuildKit's layer caching.

Steps:

Log in to the image registry (like Docker Hub, Elastic Container Registry (ECR), and Quay, to name a few).

It's worth noting that both GitLab and GitHub have their own registries for use within your repositories (both public and private) on their platforms -- GitLab Container Registry and GitHub Packages, respectively.

Use Docker build's --cache-from option to use the existing image as the cache source.

  1. Push the new image to the registry if the build is successful.

Let's look at how to do this on CircleCI, GitLab CI, and GitHub Actions, using both single and multi-stage Docker builds with and without Docker Compose. Each of the examples use Docker Hub as the image registry with REGISTRY_USER and REGISTRY_PASS set as variables in the CI builds in order to push to and pull from the registry.

Make sure to set REGISTRY_USER and REGISTRY_PASS as environment variables in the build environment:

  1. CircleCI
  2. GitLab CI
  3. GitHub Actions

Single-stage Builds

CircleCI:

# _config-examples/single-stage/circle.yml

version: 2.1

jobs:
  build:
    machine:
      image: ubuntu-2004:202010-01
    environment:
      CACHE_IMAGE: mjhea0/docker-ci-cache
      DOCKER_BUILDKIT: 1
    steps:
      - checkout
      - run:
          name: Log in to docker hub
          command: docker login -u $REGISTRY_USER -p $REGISTRY_PASS
      - run:
          name: Build from dockerfile
          command: |
            docker build \
              --cache-from $CACHE_IMAGE:latest \
              --tag $CACHE_IMAGE:latest \
              --build-arg BUILDKIT_INLINE_CACHE=1 \
              "."
      - run:
          name: Push to docker hub
          command: docker push $CACHE_IMAGE:latest

GitLab CI:

# _config-examples/single-stage/.gitlab-ci.yml

image: docker:stable
services:
  - docker:dind

variables:
  DOCKER_DRIVER: overlay2
  CACHE_IMAGE: mjhea0/docker-ci-cache
  DOCKER_BUILDKIT: 1

stages:
  - build

docker-build:
  stage: build
  before_script:
    - docker login -u $REGISTRY_USER -p $REGISTRY_PASS
  script:
    - docker build
        --cache-from $CACHE_IMAGE:latest
        --tag $CACHE_IMAGE:latest
        --file ./Dockerfile
        --build-arg BUILDKIT_INLINE_CACHE=1
        "."
  after_script:
    - docker push $CACHE_IMAGE:latest

GitHub Actions:

# _config-examples/single-stage/github.yml

name: Docker Build

on: [push]

env:
  CACHE_IMAGE: mjhea0/docker-ci-cache
  DOCKER_BUILDKIT: 1

jobs:
  build:
    name: Build Docker Image
    runs-on: ubuntu-latest
    steps:
      - name: Checkout master
        uses: actions/checkout@v1
      - name: Log in to docker hub
        run: docker login -u ${{ secrets.REGISTRY_USER }} -p ${{ secrets.REGISTRY_PASS }}
      - name: Build from dockerfile
        run: |
          docker build \
            --cache-from $CACHE_IMAGE:latest \
            --tag $CACHE_IMAGE:latest \
            --build-arg BUILDKIT_INLINE_CACHE=1 \
            "."
      - name: Push to docker hub
        run: docker push $CACHE_IMAGE:latest

Compose

If you're using Docker Compose, you can add the cache_from option to the compose file, which maps back to the docker build --cache-from <image> command when you run docker-compose build.

Example:

version: '3.8'

services:

  web:
    build:
      context: .
      cache_from:
        - mjhea0/docker-ci-cache:latest
    image: mjhea0/docker-ci-cache:latest

To take advantage of BuildKit, make sure you're using a version of Docker Compose >= 1.25.0. To enable BuildKit, set the DOCKER_BUILDKIT and COMPOSE_DOCKER_CLI_BUILD environment variables to 1. Then, again, to turn on the inline layer caching, use the BUILDKIT_INLINE_CACHE build argument.

CircleCI:

# _config-examples/single-stage/compose/circle.yml

version: 2.1

jobs:
  build:
    machine:
      image: ubuntu-2004:202010-01
    environment:
      CACHE_IMAGE: mjhea0/docker-ci-cache
      DOCKER_BUILDKIT: 1
      COMPOSE_DOCKER_CLI_BUILD: 1
    steps:
      - checkout
      - run:
          name: Log in to docker hub
          command: docker login -u $REGISTRY_USER -p $REGISTRY_PASS
      - run:
          name: Build images
          command: docker-compose build --build-arg BUILDKIT_INLINE_CACHE=1
      - run:
          name: Push to docker hub
          command: docker push $CACHE_IMAGE:latest

GitLab CI:

# _config-examples/single-stage/compose/.gitlab-ci.yml

image: docker/compose:latest
services:
  - docker:dind

variables:
  DOCKER_DRIVER: overlay2
  CACHE_IMAGE: mjhea0/docker-ci-cache
  DOCKER_BUILDKIT: 1
  COMPOSE_DOCKER_CLI_BUILD: 1

stages:
  - build

docker-build:
  stage: build
  before_script:
    - docker login -u $REGISTRY_USER -p $REGISTRY_PASS
  script:
    - docker-compose build --build-arg BUILDKIT_INLINE_CACHE=1
  after_script:
    - docker push $CACHE_IMAGE:latest

GitHub Actions:

# _config-examples/single-stage/compose/github.yml

name: Docker Build

on: [push]

env:
  CACHE_IMAGE: mjhea0/docker-ci-cache
  DOCKER_BUILDKIT: 1
  COMPOSE_DOCKER_CLI_BUILD: 1

jobs:
  build:
    name: Build Docker Image
    runs-on: ubuntu-latest
    steps:
      - name: Checkout master
        uses: actions/checkout@v1
      - name: Log in to docker hub
        run: docker login -u ${{ secrets.REGISTRY_USER }} -p ${{ secrets.REGISTRY_PASS }}
      - name: Build Docker images
        run: docker-compose build --build-arg BUILDKIT_INLINE_CACHE=1
      - name: Push to docker hub
        run: docker push $CACHE_IMAGE:latest

Multi-stage Builds

With the multi-stage build pattern, you'll have to apply the same workflow (build, then push) for each intermediate stage since those images are discarded before the final image is created. The --target option can be used to build each stage of the multi-stage build separately.

Dockerfile.multi:

# base
FROM python:3.9.7 as base
COPY ./requirements.txt /
RUN pip wheel --no-cache-dir --no-deps --wheel-dir /wheels -r requirements.txt

# stage
FROM python:3.9.7-slim
RUN apt-get update && \
    apt-get -y install netcat && \
    apt-get clean
WORKDIR /usr/src/app
COPY --from=base /wheels /wheels
COPY --from=base requirements.txt .
RUN pip install --no-cache /wheels/*
COPY . /usr/src/app
CMD gunicorn -b 0.0.0.0:5000 manage:app

CircleCI:

# _config-examples/multi-stage/circle.yml

version: 2.1

jobs:
  build:
    machine:
      image: ubuntu-2004:202010-01
    environment:
      CACHE_IMAGE: mjhea0/docker-ci-cache
      DOCKER_BUILDKIT: 1
    steps:
      - checkout
      - run:
          name: Log in to docker hub
          command: docker login -u $REGISTRY_USER -p $REGISTRY_PASS
      - run:
          name: Build base from dockerfile
          command: |
            docker build \
              --target base \
              --cache-from $CACHE_IMAGE:base \
              --tag $CACHE_IMAGE:base \
              --file ./Dockerfile.multi \
              --build-arg BUILDKIT_INLINE_CACHE=1 \
              "."
      - run:
          name: Build stage from dockerfile
          command: |
            docker build \
              --cache-from $CACHE_IMAGE:base \
              --cache-from $CACHE_IMAGE:stage \
              --tag $CACHE_IMAGE:stage \
              --file ./Dockerfile.multi \
              --build-arg BUILDKIT_INLINE_CACHE=1 \
              "."
      - run:
          name: Push base image to docker hub
          command: docker push $CACHE_IMAGE:base
      - run:
          name: Push stage image to docker hub
          command: docker push $CACHE_IMAGE:stage

GitLab CI:

# _config-examples/multi-stage/.gitlab-ci.yml

image: docker:stable
services:
  - docker:dind


variables:
  DOCKER_DRIVER: overlay2
  CACHE_IMAGE: mjhea0/docker-ci-cache
  DOCKER_BUILDKIT: 1

stages:
  - build

docker-build:
  stage: build
  before_script:
    - docker login -u $REGISTRY_USER -p $REGISTRY_PASS
  script:
    - docker build
        --target base
        --cache-from $CACHE_IMAGE:base
        --tag $CACHE_IMAGE:base
        --file ./Dockerfile.multi
        --build-arg BUILDKIT_INLINE_CACHE=1
        "."
    - docker build
        --cache-from $CACHE_IMAGE:base
        --cache-from $CACHE_IMAGE:stage
        --tag $CACHE_IMAGE:stage
        --file ./Dockerfile.multi
        --build-arg BUILDKIT_INLINE_CACHE=1
        "."
  after_script:
    - docker push $CACHE_IMAGE:stage

GitHub Actions:

# _config-examples/multi-stage/github.yml

name: Docker Build

on: [push]

env:
  CACHE_IMAGE: mjhea0/docker-ci-cache
  DOCKER_BUILDKIT: 1

jobs:
  build:
    name: Build Docker Image
    runs-on: ubuntu-latest
    steps:
      - name: Checkout master
        uses: actions/checkout@v1
      - name: Log in to docker hub
        run: docker login -u ${{ secrets.REGISTRY_USER }} -p ${{ secrets.REGISTRY_PASS }}
      - name: Build base from dockerfile
        run: |
          docker build \
            --target base \
            --cache-from $CACHE_IMAGE:base \
            --tag $CACHE_IMAGE:base \
            --file ./Dockerfile.multi \
            --build-arg BUILDKIT_INLINE_CACHE=1 \
            "."
      - name: Build stage from dockerfile
        run: |
          docker build \
            --cache-from $CACHE_IMAGE:base \
            --cache-from $CACHE_IMAGE:stage \
            --tag $CACHE_IMAGE:stage \
            --file ./Dockerfile.multi \
            --build-arg BUILDKIT_INLINE_CACHE=1 \
            "."
      - name: Push base image to docker hub
        run: docker push $CACHE_IMAGE:base
      - name: Push stage image to docker hub
        run: docker push $CACHE_IMAGE:stage

Compose

Example compose file:

version: '3.8'

services:

  web:
    build:
      context: .
      cache_from:
        - mjhea0/docker-ci-cache:stage
    image: mjhea0/docker-ci-cache:stage

CircleCI:

# _config-examples/multi-stage/compose/circle.yml

version: 2.1

jobs:
  build:
    machine:
      image: ubuntu-2004:202010-01
    environment:
      CACHE_IMAGE: mjhea0/docker-ci-cache
      DOCKER_BUILDKIT: 1
      COMPOSE_DOCKER_CLI_BUILD: 1
    steps:
      - checkout
      - run:
          name: Log in to docker hub
          command: docker login -u $REGISTRY_USER -p $REGISTRY_PASS
      - run:
          name: Build base from dockerfile
          command: |
            docker build \
              --target base \
              --cache-from $CACHE_IMAGE:base \
              --tag $CACHE_IMAGE:base \
              --file ./Dockerfile.multi \
              --build-arg BUILDKIT_INLINE_CACHE=1 \
              "."
      - run:
          name: Build Docker images
          command: docker-compose -f docker-compose.multi.yml build --build-arg BUILDKIT_INLINE_CACHE=1
      - run:
          name: Push base image to docker hub
          command: docker push $CACHE_IMAGE:base
      - run:
          name: Push stage image to docker hub
          command: docker push $CACHE_IMAGE:stage

GitLab CI:

# _config-examples/multi-stage/compose/.gitlab-ci.yml

image: docker/compose:latest
services:
  - docker:dind

variables:
  DOCKER_DRIVER: overlay
  CACHE_IMAGE: mjhea0/docker-ci-cache
  DOCKER_BUILDKIT: 1
  COMPOSE_DOCKER_CLI_BUILD: 1

stages:
  - build

docker-build:
  stage: build
  before_script:
    - docker login -u $REGISTRY_USER -p $REGISTRY_PASS
  script:
    - docker build
        --target base
        --cache-from $CACHE_IMAGE:base
        --tag $CACHE_IMAGE:base
        --file ./Dockerfile.multi
        --build-arg BUILDKIT_INLINE_CACHE=1
        "."
    - docker-compose -f docker-compose.multi.yml build --build-arg BUILDKIT_INLINE_CACHE=1
  after_script:
    - docker push $CACHE_IMAGE:base
    - docker push $CACHE_IMAGE:stage

GitHub Actions:

# _config-examples/multi-stage/compose/github.yml

name: Docker Build

on: [push]

env:
  CACHE_IMAGE: mjhea0/docker-ci-cache
  DOCKER_BUILDKIT: 1
  COMPOSE_DOCKER_CLI_BUILD: 1

jobs:
  build:
    name: Build Docker Image
    runs-on: ubuntu-latest
    steps:
      - name: Checkout master
        uses: actions/checkout@v1
      - name: Log in to docker hub
        run: docker login -u ${{ secrets.REGISTRY_USER }} -p ${{ secrets.REGISTRY_PASS }}
      - name: Build base from dockerfile
        run: |
          docker build \
            --target base \
            --cache-from $CACHE_IMAGE:base \
            --tag $CACHE_IMAGE:base \
            --file ./Dockerfile.multi \
            --build-arg BUILDKIT_INLINE_CACHE=1 \
            "."
      - name: Build images
        run: docker-compose -f docker-compose.multi.yml build --build-arg BUILDKIT_INLINE_CACHE=1
      - name: Push base image to docker hub
        run: docker push $CACHE_IMAGE:base
      - name: Push stage image to docker hub
        run: docker push $CACHE_IMAGE:stage

Conclusion

The caching strategies outlined in this article should work well for single-stage builds and multi-stage builds with two or three stages.

Each stage added to a build step requires a new build and push along with the addition of the --cache-from options for each parent stage. Thus, each new stage will add more clutter, making the CI file increasingly more difficult to read. Fortunately, BuildKit supports multi-stage builds with Docker layer caching built using a single stage. Review the following articles for more info on such advanced BuildKit patterns:

  1. Advanced Dockerfiles: Faster Builds and Smaller Images Using BuildKit and Multistage Builds
  2. Docker build cache sharing on multi-hosts with BuildKit and buildx
  3. Speed up multi-stage Docker builds in CI/CD with Buildkit’s registry cache

Finally, it's important to note that while caching may speed up your CI builds, you should re-build your images without cache from time to time in order to download the latest OS patches and security updates. For more on this, review this thread.

--

The code can be found in the docker-ci-cache repo:

  1. Single-stage examples
  2. Multi-stage examples

Cheers!

Original article source at: https://testdriven.io/

#docker #caching 

How to Faster CI Builds with Docker Layer Caching and BuildKit
Rupert  Beatty

Rupert Beatty

1666942140

Siesta: The Civilized Way to Write REST API Clients for IOS / MacOS

Siesta

The elegant way to write iOS / macOS REST clients

Drastically simplifies app code by providing a client-side cache of observable models for RESTful resources.

  • OS: iOS 10+, macOS 10.11+, tvOS 9.0+
  • Languages: Written in Swift, supports apps in both Swift and Objective-C
  • Tool requirements: Xcode 12+, Swift 5.3+ (See swift-* branches for legacy support)

What’s It For?

The Problem

Want your app to talk to a remote API? Welcome to your state nightmare!

You need to display response data whenever it arrives. Unless the requesting screen is no longer visible. Unless some other currently visible bit of UI happens to need the same data. Or is about to need it.

You should show a loading indicator (but watch out for race conditions that leave it stuck spinning forever), display user-friendly errors (but not redundantly — no modal alert dogpiles!), give users a retry mechanism … and hide all of that when a subsequent request succeeds.

Be sure to avoid redundant requests — and redundant response deserialization. Deserialization should be on a background thread, of course. Oh, and remember not to retain your ViewController / model / helper thingy by accident in your callback closures. Unless you’re supposed to.

Naturally you’ll want to rewrite all of this from scratch in a slightly different ad hoc way for every project you create.

What could possibly go wrong?

The Solution

Siesta ends this headache by providing a resource-centric alternative to the familiar request-centric approach.

Siesta provides an app-wide observable model of a RESTful resource’s state. This model answers three basic questions:

  • What is the latest data for this resource, if any?
  • Did the latest request result in an error?
  • Is there a request in progress?

…and broadcasts notifications whenever the answers to these questions change.

Siesta handles all the transitions and corner cases to deliver these answers wrapped up with a pretty bow on top, letting you focus on your logic and UI.

Features

  • Decouples view, model, and controller lifecycle from network request lifecycle
  • Decouples request initiation from request configuration
  • Eliminates error-prone state tracking logic
  • Eliminates redundant network requests
  • Unified handling for all errors: encoding, network, server-side, and parsing
  • Highly extensible, multithreaded response deserialization
  • Transparent built-in parsing (which you can turn off) for JSON, text, and images
  • Smooth progress reporting that accounts for upload, download, and latency
  • Transparent Etag / If-Modified-Since handling
  • Prebaked UI helpers for loading & error handling, remote images
  • Debug-friendly, customizable logging
  • Written in Swift with a great Swift-centric API, but…
  • …also works great from Objective-C thanks to a compatibility layer.
  • Lightweight. Won’t achieve sentience and attempt to destroy you.
  • Robust regression tests
  • Documentation and more documentation

What it doesn’t do

  • It doesn’t reinvent networking. Siesta delegates network operations to your library of choice (URLSession by default, or Alamofire, or inject your own custom adapter).
  • It doesn’t hide HTTP. On the contrary, Siesta strives to expose the full richness of HTTP while providing conveniences to simplify common usage patterns. You can devise an abstraction layer to suit your own particular needs, or work directly with Siesta’s nice APIs for requests and response entities.
  • It doesn’t do automatic response ↔ model mapping. This means that Siesta doesn’t constrain your response models, or force you to have any at all. Add a response transformer to output models of whatever flavor your app prefers, or work directly with parsed JSON.

Origin

This project started as helper code we wrote out of practical need on several Bust Out Solutions projects. When we found ourselves copying the code between projects, we knew it was time to open source it.

For the open source transition, we took the time to rewrite our code in Swift — and rethink it in Swift, embracing the language to make the API as clean as the concepts.

Siesta’s code is therefore both old and new: battle-tested on the App Store, then reincarnated in a Swifty green field.

Design Philosophy

Make the default thing the right thing most of the time.

Make the right thing easy all of the time.

Build from need. Don’t invent solutions in search of problems.

Design the API with these goals:

  1. Make client code easy to read.
  2. Make client code easy to write.
  3. Keep the API clean.
  4. Keep the implementation tidy.

…in that order of priority.


Installation

Siesta requires Swift 5.3+ and Xcode 12+. (Use the swift-* branches branches if you are still on an older version.)

Swift Package Manager

In Xcode:

  • File → Swift Packages → Add Package Dependency…
  • Enter https://github.com/bustoutsolutions/siesta in the URL field and click Next.
  • The defaults for the version settings are good for most projects. Click Next.
  • Check the checkbox next to “Siesta.”
    • Also check “SiestaUI” if you want to use any of the UI helpers.
    • Also check “Siesta_Alamofire” if you want to use the Alamofire extension for Siesta.
  • Click “Finish.”

Please note that Xcode will show all of Siesta’s optional and test-only dependencies, including Quick, Nimble, and Alamofire. Don’t worry: these won’t actually be bundled into your app (except Alamofire, if you use it).

CocoaPods

In your Podfile:

pod 'Siesta', '~> 1.0'

If you want to use the UI helpers:

pod 'Siesta/UI', '~> 1.0'

If you want to use Alamofire as your networking provider instead of Foundation’s URLSession:

pod 'Siesta/Alamofire', '~> 1.0'

(You’ll also need to pass an Alamofire.Manager when you configure your Siesta.Service. See the API docs for more info.)

Carthage

In your Cartfile:

github "bustoutsolutions/siesta" ~> 1.0

Follow the Carthage instructions to add Siesta.framework to your project. If you want to use the UI helpers, you will also need to add SiestaUI.framework to your project as well.

As of this writing, there is one additional step you need to follow that isn’t in the Carthage docs:

  • Build settings → Framework search paths → $(PROJECT_DIR)/Carthage/Build/iOS/

(In-depth discussion of Carthage in recent Xcode versions is here.)

The code in Extensions/ is not part of the Siesta.framework that Carthage builds. (This includes optional integrations for other libraries, such as Alamofire.) You will need to include those source files in your project manually if you want to use them.

Git Submodule

Clone Siesta as a submodule into the directory of your choice, in this case Libraries/Siesta:

git submodule add https://github.com/bustoutsolutions/siesta.git Libraries/Siesta
git submodule update --init

Drag Siesta.xcodeproj into your project tree as a subproject.

Under your project's Build Phases, expand Target Dependencies. Click the + button and add Siesta.

Expand the Link Binary With Libraries phase. Click the + button and add Siesta.

Click the + button in the top left corner to add a Copy Files build phase. Set the directory to Frameworks. Click the + button and add Siesta.

If you want to use the UI helpers, you will need to repeat steps 3–5 for SiestaUI.

Installation troubles?

Please let us know about it, even if you eventually figure it out. Knowing where people get stuck will help improve these instructions!


Basic Usage

Make a shared service instance for the REST API you want to use:

let MyAPI = Service(baseURL: "https://api.example.com")

Now register your view controller — or view, internal glue class, reactive signal/sequence, anything you like — to receive notifications whenever a particular resource’s state changes:

override func viewDidLoad() {
    super.viewDidLoad()

    MyAPI.resource("/profile").addObserver(self)
}

Use those notifications to populate your UI:

func resourceChanged(_ resource: Resource, event: ResourceEvent) {
    nameLabel.text = resource.jsonDict["name"] as? String
    colorLabel.text = resource.jsonDict["favoriteColor"] as? String

    errorLabel.text = resource.latestError?.userMessage
}

Or if you don’t like delegates, Siesta supports closure observers:

MyAPI.resource("/profile").addObserver(owner: self) {
    [weak self] resource, _ in

    self?.nameLabel.text = resource.jsonDict["name"] as? String
    self?.colorLabel.text = resource.jsonDict["favoriteColor"] as? String

    self?.errorLabel.text = resource.latestError?.userMessage
}

Note that no actual JSON parsing occurs when we invoke jsonDict. The JSON has already been parsed off the main thread, in a GCD queue — and unlike other frameworks, it is only parsed once no matter how many observers there are.

Of course, you probably don’t want to work with raw JSON in all your controllers. You can configure Siesta to automatically turn raw responses into models:

MyAPI.configureTransformer("/profile") {  // Path supports wildcards
    UserProfile(json: $0.content)         // Create models however you like
}

…and now your observers see models instead of JSON:

MyAPI.resource("/profile").addObserver(owner: self) {
    [weak self] resource, _ in
    self?.showProfile(resource.typedContent())  // Response now contains UserProfile instead of JSON
}

func showProfile(profile: UserProfile?) {
    ...
}

Trigger a staleness-aware, redundant-request-suppressing load when the view appears:

override func viewWillAppear(_ animated: Bool) {
    MyAPI.resource("/profile").loadIfNeeded()
}

…and you have a networked UI.

Add a loading indicator:

MyAPI.resource("/profile").addObserver(owner: self) {
    [weak self] resource, event in

    self?.activityIndicator.isHidden = !resource.isLoading
}

…or better yet, use Siesta’s prebaked ResourceStatusOverlay view to get an activity indicator, a nicely formatted error message, and a retry button for free:

class ProfileViewController: UIViewController, ResourceObserver {
    @IBOutlet weak var nameLabel, colorLabel: UILabel!

    @IBOutlet weak var statusOverlay: ResourceStatusOverlay!

    override func viewDidLoad() {
        super.viewDidLoad()

        MyAPI.resource("/profile")
            .addObserver(self)
            .addObserver(statusOverlay)
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        MyAPI.resource("/profile").loadIfNeeded()
    }

    func resourceChanged(_ resource: Resource, event: ResourceEvent) {
        nameLabel.text  = resource.jsonDict["name"] as? String
        colorLabel.text = resource.jsonDict["favoriteColor"] as? String
    }
}

Note that this example is not toy code. Together with its storyboard, this small class is a fully armed and operational REST-backed user interface.

Your socks still on?

Take a look at AFNetworking’s venerable UIImageView extension for asynchronously loading and caching remote images on demand. Seriously, go skim that code and digest all the cool things it does. Take a few minutes. I’ll wait. I’m a README. I’m not going anywhere.

Got it? Good.

Here’s how you implement the same functionality using Siesta:

class RemoteImageView: UIImageView {
  static var imageCache: Service = Service()
  
  var placeholderImage: UIImage?
  
  var imageURL: URL? {
    get { return imageResource?.url }
    set { imageResource = RemoteImageView.imageCache.resource(absoluteURL: newValue) }
  }
  
  var imageResource: Resource? {
    willSet {
      imageResource?.removeObservers(ownedBy: self)
      imageResource?.cancelLoadIfUnobserved(afterDelay: 0.05)
    }
    
    didSet {
      imageResource?.loadIfNeeded()
      imageResource?.addObserver(owner: self) { [weak self] _,_ in
        self?.image = self?.imageResource?.typedContent(
            ifNone: self?.placeholderImage)
      }
    }
  }
}

A thumbnail of both versions, for your code comparing pleasure:

Code comparison

The same functionality. Yes, really.

(Well, OK, they’re not exactly identical. The Siesta version has more robust caching behavior, and will automatically update an image everywhere it is displayed if it’s refreshed.)

There’s a more featureful version of RemoteImageView already included with Siesta — but the UI freebies aren’t the point. “Less code” isn’t even the point. The point is that Siesta gives you an elegant abstraction that solves problems you actually have, making your code simpler and less brittle.

Comparison With Other Frameworks

Popular REST / networking frameworks have different primary goals:

  • URLSession is Apple’s standard iOS HTTP library (and is all most projects need).
  • Siesta untangles state problems with an observable resource cache.
  • Alamofire provides a Swifty, modern-feeling wrapper for URLSession.
  • Moya wraps Alamofire to hide HTTP URLs and parameters.
  • RestKit couples HTTP with JSON ↔ object model ↔ Core Data mapping.
  • AFNetworking is a modern-feeling Obj-C wrapper for Apple’s network APIs, plus a suite of related utilities.

Which one is right for your project? It depends on your needs and your tastes.

Siesta has robust functionality, but does not attempt to solve everything. In particular, Moya and RestKit address complementary / alternative concerns, while Alamofire and AFNetworking provide more robust low-level HTTP support. Further complicating a comparison, some frameworks are built on top of others. When you use Moya, for example, you’re also signing up for Alamofire. Siesta uses URLSession by default, but can also stack on top of Alamofire if you want to use its SSL trust management features. Combinations abound.

With all that in mind, here is a capabilities comparison¹:

 SiestaAlamofireRestKitMoyaAFNetworkingURLSession
HTTP requests
Async response callbacks
Observable in-memory cache     
Prevents redundant requests     
Prevents redundant parsing     
Parsing for common formats   
Route-based parsing    
Content-type-based parsing     
File upload/download tasks ~ 
Object model mapping     
Core data integration     
Hides HTTP     
UI helpers    
Primary languageSwiftSwiftObj-CSwiftObj-CObj-C
Nontrivial lines of code²260939801322011783936?
Built on top ofany (injectable)URLSessionAFNetworkingAlamofireNSURLSession / NSURLConnectionApple guts

1. Disclaimer: table compiled by Siesta’s non-omniscient author. Corrections / additions? Please submit a PR
2. “Trivial” means lines containing only whitespace, comments, parens, semicolons, and braces.

Despite this capabilities list, Siesta is a relatively lean codebase — smaller than Alamofire, and 5.5x lighter than RestKit.

What sets Siesta apart?

It’s not just the features. Siesta solves a different problem than other REST frameworks.

Other frameworks essentially view HTTP as a form of RPC. New information arrives only in responses that are coupled to requests — the return values of asynchronous functions.

Siesta puts the the “ST” back in “REST”, embracing the notion of state transfer as an architectural principle, and decoupling the act of observing state from the act of transferring it.

If that approach sounds appealing, give Siesta a try.


Documentation

Examples

This repo includes a simple example project. To download the example project, install its dependencies, and run it locally:

  1. Install CocoaPods ≥ 1.0 if you haven’t already.
  2. pod try Siesta (Note that there’s no need to download/clone Siesta locally first; this command does that for you.)

Support

To ask for help, please post a question on Stack Overflow and tag it with siesta-swift. (Be sure to include that tag. It triggers a notification to the Siesta core team.) This is preferable to filing an issue because other people may have the same question as you, and Stack Overflow answers are more discoverable than closed issues.

Things that belong on Stack Overflow:

  • “How do I…?”
  • “Is there a way to…?”
  • “Is Siesta appropriate for…?”
  • “I got this error…”

For a bug, feature request, or cool idea, please file a Github issue. Things that belong in Github issues:

  • “When I do x, I expect y but get _z_”
  • “There should be a way to…”
  • “Documentation for x is missing / confusing”

Unsure which to choose? If you’re proposing a change to Siesta, use Github issues. If you’re asking a question that doesn’t change the project, and thus will remain valid even after you get an answer, then use Stack Overflow.

Two big little things

Keep in mind that Siesta is maintained by volunteers. Please be patient if you don’t immediately get an answer to your question; we all have jobs, families, obligations, and lives beyond this project.

Please be excellent to one another and follow our code of conduct.

Documentation

  • User Guide — Extensive! Thrilling! Full of examples!
  • API Docs — Lascivious detail! Hidden treasure! More examples!
  • Specs — OK, doesn’t sound glamorous, but surprisingly informative.

Download Details:

Author: Bustoutsolutions
Source Code: https://github.com/bustoutsolutions/siesta 
License: MIT license

#swift #macos #caching #ios 

Siesta: The Civilized Way to Write REST API Clients for IOS / MacOS
Lawrence  Lesch

Lawrence Lesch

1664346550

Superagent-cache: A Global Superagent Patch for Customizable Caching

Superagent-cache

Superagent with flexible built-in caching.

Now compatible with superagent 2.x and 3.x.

Note: superagent-cache is a global patch for superagent. If you prefer the same built-in caching as a superagent plugin, see superagent-cache-plugin.

Upgrading from an older version or seeing a bug? Please see the Breaking Change History section.

Basic Usage

Require and instantiate superagent-cache as follows to get the default configuration:

var superagent = require('superagent');
require('superagent-cache')(superagent);

Now you're ready for the magic! All of your existing GET and HEAD requests will be cached with no extra bloat in your queries! Any matching DELETE, POST, PUT, or PATCH requests will automatically invalidate the associated cache key and value.

superagent
  .get(uri)
  .end(function (err, response){
    // response is now cached!
    // subsequent calls to this superagent request will now fetch the cached response
  }
);

If you are using import syntax, setup is as follows:

import superagent from 'superagent';
import superagentCache from 'superagent-cache';

superagentCache(superagent);

Enjoy!

Install

npm install superagent-cache --save

Run Tests

npm test

How Does it Work?

superagent-cache patches superagent so that it can evaluate HTTP calls you make. Whenever a GET or HEAD request is made, superagent-cache generates a cache key by stringifying four properties:

  • your cache's nameSpace attribute (defaults to undefined if the property is not set)
  • you request's URI
  • your request's query params whether they're passed as an object or a string
  • your request's headers

With the generated cache key, superagent-cache then checks its internal cache instance (which you have full power to configure). If the key exists, superagent-cache returns it without performing the HTTP request and if the key does not exist, it makes the request, caches the response object (mostly), and returns it.

What Exactly Gets Cached?

If you don't use the .prune() or .responseProp() chainables detailed in the API, then superagent-cache will cache a gutted version of the response object. There are two reasons it doesn't just cache the entire response object:

  • The object is almost always circular and therefore not feasible to serialize
  • The object is huge and would use way more space than necessary

superagent-cache takes all of the following properties from the response object and clones each of them into a new object which then gets cached:

  • response.body
  • response.text
  • response.headers
  • response.statusCode
  • response.status
  • response.ok

If you find yourself occasionally needing more than this, try out the .prune() or .responseProp() chainables. If your find yourself consistently needing more than this, make a pull request that adds the properties you need.

Where does superagent-cache store data?

By default, superagent-cache stores data in a bundled instance of cacheModule, but it can natively handle any cache that matches cache-service's API. See this list of supported caches to see what works best with your use case. Because cache-service and all of the supported caches have identical APIs, superagent-cache doesn't care which you use, so pick the one that's best for you or make a new one.

What Does the Default Configuration Give Me?

You get the 'default configuration' when you only provide a superagent instance to the require('superagent-cache')() command. This will patch the passed instance of superagent and bundle an instance of cacheModule for storing data. cacheModule is a slim, in-memory cache.

How Do I Use a Custom Configuration?

To use a custom configuraiton, take advantage of the the two optional params you can hand to superagent-cache's require command (cache, and defaults) as follows:

//Require superagent and the cache module I want
var superagent = require('superagent');
var redisModule = require('cache-service-redis');
var redisCache = new redisModule({redisEnv: 'REDISCLOUD_URL'});
var defaults = {cacheWhenEmpty: false, expiration: 900};

//Patch my superagent instance and pass in my redis cache
require('superagent-cache')(superagent, redisCache, defaults);

This example patches your instance of superagent as allowing you to pass in your own, pre-instantiated cache and some defaults for superagent-cache to use with all queries. Here's a list of supported caches.

The cache param can be either a pre-instantiated cache module, or a cacheModuleConfig object to be used with superagent-cache's bundled cacheModule instance.

All data passed in the defaults object will apply to all queries made with superagent-cache unless overwritten with chainables. See the Available Configuration Options section for a list of all options you can pass.

For more information on require command params usage, see this section.

Available Configuration Options

All options that can be passed to the defaults require param can be overwritten with chainables of the same name. All of the below options are detailed in the API section.

  • responseProp
  • prune
  • pruneQuery
  • pruneHeader
  • expiration
  • cacheWhenEmpty
  • doQuery
  • forceUpdate
  • preventDuplicateCalls
  • backgroundRefresh

Supported Caches

cache-service

A tiered caching solution capable of wrapping any number of the below supported caches. Available on NPM.

cache-service-redis

A redis wrapper for cache-service or standalone use. Available on NPM.

cache-service-memcached

A memcached wrapper for cache-service or standalone use. Available on NPM.

cache-service-node-cache

An in-memory cache wrapper for cache-service or standalone use. Available on NPM.

cache-service-cache-module

A super-light in-memory cache for cache-service or standalone use. (This module is bundled with superagent-cache and provided in the default configuration if you do not provide a cache require param.) Available on NPM.

API

require('superagent-cache')(superagent, [cache, defaults])

superagent is required, but cache and defaults are optional.

Arguments

  • superagent: an instance of superagent
  • (optional) cache: a pre-instantiated cache module that matches the cache-service API or a cacheModuleConfig object to be used with superagent-cache's bundled instance of cacheModule
  • (optional) defaults: an object that allows you to set defaults to be applied to all queries

.get(uri), .head(uri)

Same as superagent except that superagent's response object will be cached.

.put(uri), .post(uri), .patch(uri), .del(uri)

Same as superagent except that the generated cache key will be automatically invalidated when these HTTP verbs are used.

.end(callback ([err,] response [, key]))

Same as superagent except it optionally exposes the key superagent-cache generates as the third param in the callback's argument list. See the usage example for a more detailed explanation.

.responseProp(prop)

Caution: if you use this function, supergent-cache will not gut the response object for you. Be sure that the result of your .responseProp() call will never be circular and is not larger than it needs to be. Consider using .prune() if you need to dig several layers into the response object.

If you know you want a single, top-level property from superagent's response object, you can optimize what you cache by passing the property's name here. When used, it causes the .end() function's response to return superagent's response[prop].

Arguments

  • prop: string

Example

//response will now be replaced with superagent's response.body
//but all other top-level response properties, such as response.ok and response.status, will be ommitted
superagent
  .get(uri)
  .responseProp('body')
  .end(function (error, response){
    // handle response
  }
);

Caution: if you use this function, supergent-cache will not automatically gut the response object for you (although you can use the gutResponse param to do so manually--more on that below). Be sure that the result of your .prune() callback function will never be circular and is not larger than it needs to be.

If you need to dig several layers into superagent's response, you can do so by passing a function to .prune(). Your prune function will receive superagent's response and should return a truthy value or null. The benefit of using this function is that you can cache only what you need.

Your prune function will optionally receive gutResponse as its second param. This is the method that superagent-cache calls internally to simplify responses. If, based on some processing within the function you pass to .prune, you decide that no custom return value is necessary, or you would like to take advantage of the default behavior plus some of your own, you can call gutResponse(response) to get the value that superagent-cache would have returned without a call to .prune.

Arguments

  • callback: a function that accepts superagent's response object and the gutResponse function and returns a truthy value or null

Example

var prune = function(r){
  return (r && r.ok && r.body && r.body.user) ? r.body.user : null;
}

//response will now be replaced with r.body.user or null
//and only r.body.user will be cached rather than the entire superagent response
superagent
  .get(uri)
  .prune(prune)
  .end(function (error, response){
    // handle response
  }
);

Example Using gutResponse

var prune = function(r, gutResponse){
  var output = gutResponse(r);
  output.customValue = getCustomValue();
  return output;
}

//response will now be superagent-cache-plugin's default output plus `customValue`
//all of which will be cached rather than the entire superagent response
superagent
  .get(uri)
  .prune(prune)
  .end(function (error, response){
    // handle response
  }
);

.pruneQuery(params)

In the event that you need certain query params to execute a query but cannot have those params as part of your cache key (useful when security or time-related params are sent), use .pruneQuery() to remove those properties. Pass .pruneQuery() an array containing the param keys you want omitted from the cache key.

Arguments

  • params: array of strings

Example

//the superagent query will be executed with all params
//but the key used to store the superagent response will be generated without the passed param keys
superagent
  .get(uri)
  .query(query)
  .pruneQuery(['token'])
  .end(function (error, response){
    // handle response
  }
);

.pruneHeader(options)

This function works just like the .pruneQuery() funciton except that it modifies the arguments passed to the .set() chainable method (headers) rather than those passed to the .query() chainable method.

Arguments

  • options: array of strings

Example

//the superagent query will be executed with all headers
//but the key used to store the superagent response will be generated without the passed header keys
superagent
  .get(uri)
  .set(options)
  .pruneHeader(['token'])
  .end(function (error, response){
    // handle response
  }
);

.expiration(seconds)

Use this function when you need to override your cache's defaultExpiration property for a particular cache entry.

Arguments

  • seconds: integer

.cacheWhenEmpty(bool)

Tell superagent-cache whether to cache the response object when it's false, null, or {}.This is especially useful when using .responseProp() or .prune() which can cause response to be falsy. By default, cacheWhenEmpty is true.

Arguments

  • bool: boolean, default: true

.doQuery(bool)

Tell superagent-cache whether to perform an ajax call if the generated cache key is not found. By default, doQuery is true.

Arguments

  • bool: boolean, default: true

.forceUpdate(bool)

Tells superagent-cache to perform an ajax call regardless of whether the generated cache key is found. By default, forceUpdate is false.

Arguments

  • bool: boolean, default: false

.preventDuplicateCalls(bool)

If you're considering using this feature, please first consider whether you can instead consolidate duplicate requests into a single service layer and get data into the necessary spots via eventing or data binding.

I added this feature to prevent duplicate calls when dynamically prefetching content based on user interactions. In my use case, I prefetch content when users hover over a link so that when they click the link it's already in the cache. In the event that the user clicks the link before the prefetch AJAX call has completed, I wanted to prevent a second network call from occurring.

When activated, superagent-cache will keep track of all pending AJAX calls. If a call is attempted while an identical call is already pending, the duplicate call will not be made. When the original AJAX call returns, its response will be used to respond to all duplicate calls.

Arguments

  • bool: boolean, default: false

.backgroundRefresh(value)

See the Using Background Refresh section for more information.

Tell the underlying cache provided in the require command to enable background refresh for the generated key and value. If a function is provided, it will use the function, if a boolean is provided, it will use the boolean, if nothing is provided, it will default to true.

Arguments

  • value: boolean || function || undefined, default: true

._superagentCache_originalEnd(callback (err, response))

This is a convenience method that allows you to skip all caching logic and use superagent as normal.

Arguments

  • callback: a function that accepts superagent's error and response objects

.cache

This is the second constructor param you handed in when you instantiated superagent-cache. If you didn't provide one, then it's an instance of cacheModule. You can assign it or call functions on it at runtime.

Example

superagent.cache... //You can call any function existing on the cache you passed in

.defaults

This is the third constructor param you handed in when you instantiated superagent-cache. If you didn't provide one, then it uses the internal defaults. You can assign it or update it at runtime.

Example

superagent.defaults... //You can call any function existing on the cache you passed in

Using Background Refresh

With a typical cache setup, you're left to find the perfect compromise between having a long expiration so that users don't have to suffer through the worst case load time, and a short expiration so data doesn't get stale. superagent-cache eliminates the need to worry about users suffering through the longest wait time by automatically refreshing keys for you.

How do I turn it on?

By default, background refresh is off. It will turn itself on the first time you use the .backgroundRefresh() chainable.

Setup

superagent-cache relies on the background refresh feature of the cache param you pass into the require command. When you use the .backgroundRefresh() chainable, superagent-cache passes the provided value into cache. This means that if you're using cache-service, you almost certainly want cache-service's writeToVolatileCaches property set to true (it defaults to true) so that the data set by background refresh will propogate forward to earlier caches (cache-service ONLY background refreshses the final cache passed to it).

Configure

If desired, configure the following properties within cache:

  • backgroundRefreshInterval
  • backgroundRefreshMinTtl
  • backgroundRefreshIntervalCheck

Use

Background refresh is exposed via the .backgroundRefresh() chainable.

When true or no param is passed to .backgroundRefresh(), it will generate a superagent call identical to the one that triggered it and pass that to cache.

superagent
  .get(uri)
  .backgroundRefresh()
  .end(function (err, response){
    //Response will now be refreshed in the background
  }
);

When a function is passed, it will use that function. Read on for background refresh function requirements.

var refresh = function(key, cb){
  var response = goGetData();
  cb(null, response);
}

superagent
  .get(uri)
  .backgroundRefresh(refresh)
  .end(function (err, response){
    //Response will now be refreshed in the background
  }
);

When false is passed, it will do nothing.

The Refresh Param

refresh(key, cb(err, response))

  • key: type: string: this is the key that is being refreshed
  • cb: type: function: you must trigger this function to pass the data that should replace the current key's value

The refresh param MUST be a function that accepts key and a callback function that accepts err and response as follows:

var refresh = function(key, cb){
  var response = goGetData();
  cb(null, response);
}

More Usage Examples

.end() callback argument list options

As an optional parameter in the .end(cb) callback argument list, superagent-cache can give you the key it generated for each query as follows:

superagent
  .get(uri)
  .end(function (err, response, key){
    console.log('GENERATED KEY:', key);
  }
);

This can be useful if you need external access to a cache key and for testing purposes.

However, you can only get it when you pass 3 params to the callback's argument list. The following rules will apply when listing arguments in the .end(cb) callback argument list:

  • 1 param: the param will always be response
  • 2 params: the params will always be err and response
  • 3 params: the params will always be err, response, and key

Various ways of requiring superagent-cache

NOTE: You must pass your own superagent instance or superagent-cache will throw an error.

When only superagent is passed

//...it will patch the provided superagent and create a cacheModule instance (see 'default configuration')
var superagent = require('superagent');
require('superagent-cache')(superagent)

When superagent and cache are passed

Example 1

//...it will patched the provided superagent instance and consume cache as its data store
var superagent = require('superagent');
var redisModule = require('cache-service-redis');
var redisCache = new redisModule({redisEnv: 'REDISCLOUD_URL'});
var superagent = require('superagent-cache')(superagent, redisCache);

Example 2

//...it will patched the provided superagent instance and consume cache as its cacheModuleConfig for use with the bundled instance of cacheModule
var superagent = require('superagent');
var cacheModuleConfig = {storage: 'session', defaultExpiration: 60};
var superagent = require('superagent-cache')(superagent, cacheModuleConfig);

With defaults

The defaults object can be passed as the third param at any time. It does not affect the superagent or cache params. You can see a brief demo here and a list of all the options you can pass in the defaults object here.

Breaking Change History

2.0.0

  • Now compatible with superagent 2.x and 3.x
  • Because superagent-cache is now compatible with superagent 1, 2, and 3, it does not bundle an instance of superagent. As a result, you must always provide your own instance to be patched.
  • ._end is now ._superagentCache_originalEnd to prevent future naming colisions
  • .pruneParams is now .pruneQuery for clarity
  • .pruneOptions is now .pruneHeader for clarity
  • The resolve function passed to .then no longer exposes the generated cache key like it did when using superagent ^1.3.0 with superagent-cache ^1.5.0 (but using .end still does)

1.3.5

In superagent 1.7.0, the superagent team introduced some internal changes to how they handle headers. As a result, you must use superagent-cache 1.3.5 or later to be compatible with superagent 1.7.0 or later. All versions of superagent-cache (to this point) should be backwards compatible with all versions of superagent going back to at least version 1.1.0. To be clear, this was no one's fault. However, I have reached out to the superagent team to see what I can do to help minimize internally breaking changes in the future.

If you're seeing other incompatibilities, please submit an issue.

1.0.6

A bug was introduced that broke superagent-cache's ability to cache while running on the client. This bug was fixed in 1.3.0. The bug did not affect superagent-cache while running on node.

0.2.0

superagent-cache is now more flexible, allowing usage of any cache that matches cache-service's API. To make it lighter, then, the hard dependency on cache-service was replaced with the much lighter cacheModule. As a result, superagent-cache can no longer construct a cache-service instance for you. If you wish to use cache-service, you must instantiate it externally and hand it in as cache--the second param in the require command.

Download Details:

Author: Jpodwys
Source Code: https://github.com/jpodwys/superagent-cache 
License: MIT license

#javascript #cache #caching 

Superagent-cache: A Global Superagent Patch for Customizable Caching
Royce  Reinger

Royce Reinger

1663936812

4 Best Caching Rust You Must Know

In today's post we will learn about 4 Best Caching Rust You Must Know.

What is caching?

Caching is the process of storing copies of files in a cache, or temporary storage location, so that they can be accessed more quickly. Technically, a cache is any temporary storage location for copies of files or data, but the term is often used in reference to Internet technologies. Web browsers cache HTML files, JavaScript, and images in order to load websites more quickly, while DNS servers cache DNS records for faster lookups and CDN servers cache content to reduce latency.

Table of contents:

  • Aisk/rust-memcache - Memcached client library.
  • Al8n/stretto - A high performance thread-safe memory-bound Rust cache.
  • Jaemk/cached - Simple function caching/memoization.
  • Mozilla/sccache - Shared Compilation Cache, great for Rust compilation.

1 - Aisk/rust-memcache:

Memcached client library.

rust-memcache is a memcached client written in pure rust.

Install

The crate is called memcache and you can depend on it via cargo:

[dependencies] memcache = "*"

Features

  •  All memcached supported protocols
    •  Binary protocol
    •  ASCII protocol
  •  All memcached supported connections
    •  TCP connection
    •  UDP connection
    •  UNIX Domain socket connection
    •  TLS connection
  •  Encodings
    •  Typed interface
    •  Automatically compress
    •  Automatically serialize to JSON / msgpack etc
  •  Memcached cluster support with custom key hash algorithm
  •  Authority
    •  Binary protocol (plain SASL authority plain)
    •  ASCII protocol

Basic usage

// create connection with to memcached server node:
let client = memcache::connect("memcache://127.0.0.1:12345?timeout=10&tcp_nodelay=true").unwrap();

// flush the database
client.flush().unwrap();

// set a string value
client.set("foo", "bar", 0).unwrap();

// retrieve from memcached:
let value: Option<String> = client.get("foo").unwrap();
assert_eq!(value, Some(String::from("bar")));
assert_eq!(value.unwrap(), "bar");

// prepend, append:
client.prepend("foo", "foo").unwrap();
client.append("foo", "baz").unwrap();
let value: String = client.get("foo").unwrap().unwrap();
assert_eq!(value, "foobarbaz");

// delete value:
client.delete("foo").unwrap();

// using counter:
client.set("counter", 40, 0).unwrap();
client.increment("counter", 2).unwrap();
let answer: i32 = client.get("counter").unwrap().unwrap();
assert_eq!(answer, 42);

Custom key hash function

If you have multiple memcached server, you can create the memcache::Client struct with a vector of urls of them. Which server will be used to store and retrive is based on what the key is.

This library have a basic rule to do this with rust's builtin hash function, and also you can use your custom function to do this, for something like you can using a have more data on one server which have more memory quota, or cluster keys with their prefix, or using consitent hash for large memcached cluster.

let mut client = memcache::connect(vec!["memcache://127.0.0.1:12345", "memcache:///tmp/memcached.sock"]).unwrap();
client.hash_function = |key: &str| -> u64 {
    // your custom hashing function here
    return 1;
};

View on Github

2 - Al8n/stretto:

A high performance thread-safe memory-bound Rust cache.

Features

  • Internal Mutability - Do not need to use Arc<RwLock<Cache<...>> for concurrent code, you just need Cache<...> or AsyncCache<...>
  • Sync and Async - Stretto support sync and runtime agnostic async.
    • In sync, Cache starts two extra OS level threads. One is policy thread, the other is writing thread.
    • In async, AsyncCache starts two extra green threads. One is policy thread, the other is writing thread.
  • Store policy Stretto only store the value, which means the cache does not store the key.
  • High Hit Ratios - with Dgraph's developers unique admission/eviction policy pairing, Ristretto's performance is best in class.
    • Eviction: SampledLFU - on par with exact LRU and better performance on Search and Database traces.
    • Admission: TinyLFU - extra performance with little memory overhead (12 bits per counter).
  • Fast Throughput - use a variety of techniques for managing contention and the result is excellent throughput.
  • Cost-Based Eviction - any large new item deemed valuable can evict multiple smaller items (cost could be anything).
  • Fully Concurrent - you can use as many threads as you want with little throughput degradation.
  • Metrics - optional performance metrics for throughput, hit ratios, and other stats.
  • Simple API - just figure out your ideal CacheBuilder/AsyncCacheBuilder values and you're off and running.

Installation

  • Use Cache.
[dependencies]
stretto = "0.7"

or

[dependencies]
stretto = { version = "0.7", features = ["sync"] }
  • Use AsyncCache
[dependencies]
stretto = { version = "0.7", features = ["async"] }
  • Use both Cache and AsyncCache
[dependencies]
stretto = { version = "0.7", features = ["full"] }

Usage

Example

Sync

use std::time::Duration;
use stretto::Cache;

fn main() {
    let c = Cache::new(12960, 1e6 as i64).unwrap();

    // set a value with a cost of 1
    c.insert("a", "a", 1);
    // set a value with a cost of 1 and ttl
    c.insert_with_ttl("b", "b", 1, Duration::from_secs(3));

    // wait for value to pass through buffers
    c.wait().unwrap();

    // when we get the value, we will get a ValueRef, which contains a RwLockReadGuard
    // so when we finish use this value, we must release the ValueRef
    let v = c.get(&"a").unwrap();
    assert_eq!(v.value(), &"a");
    v.release();

    // lock will be auto released when out of scope
    {
        // when we get the value, we will get a ValueRef, which contains a RwLockWriteGuard
        // so when we finish use this value, we must release the ValueRefMut
        let mut v = c.get_mut(&"a").unwrap();
        v.write("aa");
        assert_eq!(v.value(), &"aa");
        // release the value
    }

    // if you just want to do one operation
    let v = c.get_mut(&"a").unwrap();
    v.write_once("aaa");

    let v = c.get(&"a").unwrap();
    assert_eq!(v.value(), &"aaa");
    v.release();

    // clear the cache
    c.clear().unwrap();
    // wait all the operations are finished
    c.wait().unwrap();
    assert!(c.get(&"a").is_none());
}

View on Github

3 - Jaemk/cached:

Simple function caching/memoization.

cached provides implementations of several caching structures as well as a handy macros for defining memoized functions.

Memoized functions defined using #[cached]/#[once]/#[io_cached]/cached! macros are thread-safe with the backing function-cache wrapped in a mutex/rwlock, or externally synchronized in the case of #[io_cached]. By default, the function-cache is not locked for the duration of the function's execution, so initial (on an empty cache) concurrent calls of long-running functions with the same arguments will each execute fully and each overwrite the memoized value as they complete. This mirrors the behavior of Python's functools.lru_cache. To synchronize the execution and caching of un-cached arguments, specify #[cached(sync_writes = true)] / #[once(sync_writes = true)] (not supported by #[io_cached].

Features

  • default: Include proc_macro and async features
  • proc_macro: Include proc macros
  • async: Include support for async functions and async cache stores
  • redis_store: Include Redis cache store
  • redis_async_std: Include async Redis support using async-std and async-std tls support, implies redis_store and async
  • redis_tokio: Include async Redis support using tokio and tokio tls support, implies redis_store and async
  • wasm: Enable WASM support. Note that this feature is incompatible with all Redis features (redis_store, redis_async_std, redis_tokio)

The procedural macros (#[cached], #[once], #[io_cached]) offer more features, including async support. See the proc_macro and macros modules for more samples, and the examples directory for runnable snippets.`

Any custom cache that implements cached::Cached/cached::CachedAsync can be used with the #[cached]/#[once]/cached! macros in place of the built-ins. Any custom cache that implements cached::IOCached/cached::IOCachedAsync can be used with the #[io_cached] macro.


The basic usage looks like:

use cached::proc_macro::cached;

/// Defines a function named `fib` that uses a cache implicitly named `FIB`.
/// By default, the cache will be the function's name in all caps.
/// The following line is equivalent to #[cached(name = "FIB", unbound)]
#[cached]
fn fib(n: u64) -> u64 {
    if n == 0 || n == 1 { return n }
    fib(n-1) + fib(n-2)
}

use std::thread::sleep;
use std::time::Duration;
use cached::proc_macro::cached;
use cached::SizedCache;

/// Use an explicit cache-type with a custom creation block and custom cache-key generating block
#[cached(
    type = "SizedCache<String, usize>",
    create = "{ SizedCache::with_size(100) }",
    convert = r#"{ format!("{}{}", a, b) }"#
)]
fn keyed(a: &str, b: &str) -> usize {
    let size = a.len() + b.len();
    sleep(Duration::new(size as u64, 0));
    size
}

View on Github

4 - Mozilla/sccache:

Shared Compilation Cache, great for Rust compilation.

sccache is a ccache-like compiler caching tool. It is used as a compiler wrapper and avoids compilation when possible, storing cached results either on local disk or in one of several cloud storage backends.

sccache includes support for caching the compilation of C/C++ code, Rust, as well as NVIDIA's CUDA using nvcc.

sccache also provides icecream-style distributed compilation (automatic packaging of local toolchains) for all supported compilers (including Rust). The distributed compilation system includes several security features that icecream lacks such as authentication, transport layer encryption, and sandboxed compiler execution on build servers. See the distributed quickstart guide for more information.

Installation

There are prebuilt x86-64 binaries available for Windows, Linux (a portable binary compiled against musl), and macOS on the releases page. Several package managers also include sccache packages, you can install the latest release from source using cargo, or build directly from a source checkout.

macOS

On macOS sccache can be installed via Homebrew:

brew install sccache

Windows

On Windows, sccache can be installed via scoop:

scoop install sccache

Via cargo

If you have a Rust toolchain installed you can install sccache using cargo. Note that this will compile sccache from source which is fairly resource-intensive. For CI purposes you should use prebuilt binary packages.

cargo install sccache


Usage

Running sccache is like running ccache: prefix your compilation commands with it, like so:

sccache gcc -o foo.o -c foo.c

If you want to use sccache for caching Rust builds you can define build.rustc-wrapper in the cargo configuration file. For example, you can set it globally in $HOME/.cargo/config.toml by adding:

[build]
rustc-wrapper = "/path/to/sccache"

Note that you need to use cargo 1.40 or newer for this to work.

Alternatively you can use the environment variable RUSTC_WRAPPER:

export RUSTC_WRAPPER=/path/to/sccache
cargo build

sccache supports gcc, clang, MSVC, rustc, NVCC, and Wind River's diab compiler.

If you don't specify otherwise, sccache will use a local disk cache.

sccache works using a client-server model, where the server runs locally on the same machine as the client. The client-server model allows the server to be more efficient by keeping some state in memory. The sccache command will spawn a server process if one is not already running, or you can run sccache --start-server to start the background server process without performing any compilation.

You can run sccache --stop-server to terminate the server. It will also terminate after (by default) 10 minutes of inactivity.

Running sccache --show-stats will print a summary of cache statistics.

Some notes about using sccache with Jenkins are here.

To use sccache with cmake, provide the following command line arguments to cmake 3.4 or newer:

-DCMAKE_C_COMPILER_LAUNCHER=sccache
-DCMAKE_CXX_COMPILER_LAUNCHER=sccache

To generate PDB files for debugging with MSVC, you can use the /Z7 option. Alternatively, the /Zi option together with /Fd can work if /Fd names a different PDB file name for each object file created. Note that CMake sets /Zi by default, so if you use CMake, you can use /Z7 by adding code like this in your CMakeLists.txt:

if(CMAKE_BUILD_TYPE STREQUAL "Debug")
  string(REPLACE "/Zi" "/Z7" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
  string(REPLACE "/Zi" "/Z7" CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}")
elseif(CMAKE_BUILD_TYPE STREQUAL "Release")
  string(REPLACE "/Zi" "/Z7" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}")
  string(REPLACE "/Zi" "/Z7" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
elseif(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
  string(REPLACE "/Zi" "/Z7" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")
  string(REPLACE "/Zi" "/Z7" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}")
endif()

By default, sccache will fail your build if it fails to successfully communicate with its associated server. To have sccache instead gracefully failover to the local compiler without stopping, set the environment variable SCCACHE_IGNORE_SERVER_IO_ERROR=1.

View on Github

Thank you for following this article.

Related videos:

Caching Rust Builds

#rust #caching 

4 Best Caching Rust You Must Know
Hunter  Krajcik

Hunter Krajcik

1661149380

Flutter Widget for Fetching, Caching and Refetching Asynchronous Data

Flutter library for fetching, caching and invalidating asynchronous data

Quick Features

  • Fetch asynchronous data
  • Data invalidation
  • Optimistic response
  • Reset cache

Motivation

How to do API calls in Flutter? Probably, majority would answer by using Dio.
But the real question would be, how to integrate API calls in Flutter arhitecture seamless? One way would be to use FutureBuilder or maybe Bloc like most of the community do.

The thing is, both FutureBuilder and Bloc have flaws.
For example, FutureBuilder is too simple. It does provide status of your query, but communicating with some FutureBuilder is impossible outside of the scope of the current screen.
Problem with Bloc is that will include so many boilerplate code for a simple API call but again, talking to the Bloc is impossible from other screens. Bloc should be number one choice for complicated workflows, but what if you don't have any complex business logic?

flutter_requery to the rescue!

Example

Let's start with this simple example.

/// define data and cache key
final List<String> data = ["Hello"];
final String cacheKey = 'strKey';

/// simulates API call, therefore response is delayed
Future<List<String>> _getData() async {
  await Future.delayed(Duration(seconds: 1));
  return data;
}

/// later, used in the build method
Query<List<String>>(
  cacheKey,
  future: _getData,
  builder: (context, response) {
    if (response.error != null) {
      return Text('Error');
    }
    if (response.loading) {
      return Text('Loading...');
    }
    final children = response.data.map((str) => Text(str)).toList();
    return ListView(
      children: children
    );
  },
);

/// and later when you want to invalidate your data
void _onPress() async {
  await Future.delayed(Duration(seconds: 1));
  data.add("World");
  queryCache.invalidateQueries(cacheKey);
}

And that's the gist of it. You can find complete example on pub.dev.

Usage

Cache key

Every query needs a cache key.
Under this key your data will be stored in cache. It can be string, int or list of strings and ints.

/// good
const k1 = 'myKey';
const k2 = 1;
const k3 = ["data", 1]

/// bad
const k1 = 1.0;
const k2 = true;
const k3 = ["data", true];

Idea behind having keys specified as list is that you can invalidate your queries more intelligently.
Take a look at invalidation chapter for more details.

Query

Once the cache key is defined, next step is to write the query.
Query takes 3 arguments:

  • cacheKey - look here for more info.
  • future - async action which will be executed.
  • builder - follows the Flutter builder pattern. First parameter is BuildContext followed by QueryResponse object.

QueryResponse manages query status. It also has 3 properties:

  • data - response received from the future or null if the exception occured.
  • loading - boolean, true if the query is running. Otherwise, it's false.
  • error - represents an error received from the future. If the future was successful, error will be null.
// use Query widget in the build method
Query<List<String>>(
  'myCacheKey',
  future: ()async {
    await Future.delayed(Duration(seconds: 1));
    return ["Hello"]
  }
  builder: (context, QueryResponse response) {
    /// error state
    if (response.error != null) {
      return Text('Error');
    }
    /// loading state
    if (response.loading) {
      return Text('Loading...');
    }
    final children = response.data.map((str) => Text(str)).toList();
    return ListView(
      children: children
    );
  },
);

Invalidation

Data invalidation can come in two different forms.
You can either afford to wait for the API response or you simply need to show the newest data as soon as possible. If you are interested in following, check the next chapter.

Waiting for the API response is more common and flutter_requery supports this by using the queryCache instance. It's global and already defined by the library. Invalidate your query by passing the cache keys.

// invalidates strKey query
queryCache.invalidateQueries('strKey');

// support for bulk invalidation
queryCache.invalidateQueries(['strKey1', 'strKey2']);

// if your keys are lists, end result would be similar to
queryCache.invalidateQueries([
  ['strKey', 1],
  ['strKey2', 2]
]);

Once query is invalidated, every Query widget subscribed for that query will execute future again and rebuild its children with the new data.

For cache-level invalidation use:

// invalidate every query stored in cache
queryCache.invalidateAll()

Invalidation works in pair with the keys defined as lists. Cache keys defined as list must be looked upon in a hierarchical manner where the list elements defined before are ancestors of the elements that come after. For example:

// requests is ancestor of 1
const key1 = ["requests", 1]

Reasoning behind this is to support hierarchical invalidation. Sometimes it can get cumbersome managing invalidations and therefore developer can decide to cleverly name keys to support this. For example:

const k1 = ["requests", 1]
const k2 = ["requests", 2]
const k3 = "requests"

// without hierarchical invalidation you need to call
queryCache.invalidateQueries([
  ["requests", 1], ["requests", 2], "requests"
]);

// but with hierarchical invalidation you only need to call
queryCache.invalidateQueries("requests");

Optimistic response

Sometimes waiting period for the API response to be available is too long. Therefore you can immediately update the cache data and rebuild your widget tree by using the optimistic response. Make sure to remove await keyword before the API call since this will block the thread.

  queryCache.setOptimistic("requests", [...oldData, newData]);

Reset

In short, reset can be explained as cache-level invalidation without rebuilding the widget tree.
Also, async actions won't be ran immediately but only when the new Query widget is mounted or the cacheKey has changed. This is particularly useful for the log out action.

queryCache.reset();

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add flutter_requery

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:
  flutter_requery: ^0.1.14

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:flutter_requery/flutter_requery.dart';

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter_requery/flutter_requery.dart';

void main() {
  runApp(App());
}

final List<String> data = ["Hello"];

class App extends StatelessWidget {
  Future<List<String>> _getData() async {
    await Future.delayed(Duration(seconds: 1));
    return data;
  }

  Future<void> _addString() async {
    await Future.delayed(Duration(seconds: 1));
    data.add("World");
  }

  void _onPress() async {
    // Call API and invalidate your query by using the same cache key
    await _addString();
    queryCache.invalidateQueries('strKey');

    // Or if you don't want to wait and you are sure
    // your API works use optimistic response.
    // _addString();
    // queryCache.setOptimistic('strKey', [...data, 'World']);
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text("Flutter requery"),
        ),
        floatingActionButton: FloatingActionButton(
          child: Text("Add"),
          onPressed: _onPress,
        ),
        body: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Query<List<String>>(
            'strKey',
            future: _getData,
            builder: (context, response) {
              if (response.error != null) {
                return Text('Error');
              }
              if (response.loading) {
                return CircularProgressIndicator();
              }
              return ListView(
                children: response.data.map((str) => Text(str)).toList(),
              );
            },
          ),
        ),
      ),
    );
  }
}

Summary

APIDescription
QueryFlutter widget used for data-fetching operations.
queryCache.invalidateQueriesInvalidates query specified by cache key and rebuilds the widget tree.
queryCache.invalidateAllInvalidates every query stored in cache and rebuilds the widget tree.
queryCache.setOptimisticSet cache data manually and rebuild the widget tree.
queryCache.resetInvalidates every query stored in cache without rebuilding the widget tree.

Download Details:

Author: CoreLine-agency
Source Code: https://github.com/CoreLine-agency/flutter_requery 
License: MIT license

#flutter #dart #caching 

Flutter Widget for Fetching, Caching and Refetching Asynchronous Data

Learn About Caching in Django

Caching is typically the most effective way to boost an application's performance.

This article looks at how to do just that by first reviewing Django's caching framework as a whole and then detailing step-by-step how to cache a Django view.

Source: https://testdriven.io

#django #caching 

Learn About Caching in Django
Duyen Hoang

Duyen Hoang

1660907040

Tìm Hiểu Về Bộ Nhớ Đệm Trong Django

Bộ nhớ đệm thường là cách hiệu quả nhất để tăng hiệu suất của ứng dụng.

Đối với các trang web động, khi hiển thị một mẫu, bạn thường phải thu thập dữ liệu từ nhiều nguồn khác nhau (như cơ sở dữ liệu, hệ thống tệp và API của bên thứ ba, để đặt tên cho một số), xử lý dữ liệu và áp dụng logic nghiệp vụ cho nó trước khi phân phối nó cho khách hàng. Bất kỳ sự chậm trễ nào do độ trễ của mạng sẽ được người dùng cuối nhận thấy.

Ví dụ: giả sử bạn phải thực hiện một cuộc gọi HTTP tới một API bên ngoài để lấy dữ liệu cần thiết để hiển thị một mẫu. Ngay cả trong điều kiện hoàn hảo, điều này sẽ làm tăng thời gian hiển thị, điều này sẽ làm tăng thời gian tải tổng thể. Điều gì sẽ xảy ra nếu API gặp sự cố hoặc bạn có thể bị giới hạn tốc độ? Dù bằng cách nào, nếu dữ liệu không được cập nhật thường xuyên, bạn nên triển khai cơ chế lưu vào bộ nhớ đệm để tránh phải thực hiện lệnh gọi HTTP hoàn toàn cho mỗi yêu cầu của khách hàng.

Bài viết này xem xét cách thực hiện điều đó bằng cách xem xét toàn bộ khuôn khổ bộ nhớ đệm của Django trước tiên và sau đó trình bày chi tiết từng bước cách lưu vào bộ nhớ cache một dạng xem Django.

Sự phụ thuộc:

  1. Django v3.2.5
  2. django-redis v5.0.0
  3. Python v3.9.6
  4. pymemcache v3.5.0
  5. Yêu cầu v2.26.0

Mục tiêu

Đến cuối hướng dẫn này, bạn sẽ có thể:

  1. Giải thích lý do tại sao bạn có thể muốn xem xét lưu vào bộ nhớ đệm một chế độ xem Django
  2. Mô tả các tùy chọn tích hợp sẵn của Django cho bộ nhớ đệm
  3. Lưu vào bộ nhớ cache một chế độ xem Django với Redis
  4. Tải thử ứng dụng Django với Apache Bench
  5. Lưu vào bộ nhớ cache một chế độ xem Django với Memcached

Các loại bộ nhớ đệm Django

Django đi kèm với một số phụ trợ bộ nhớ đệm tích hợp, cũng như hỗ trợ cho một phụ trợ tùy chỉnh .

Các tùy chọn tích hợp là:

  1. Memcached : Memcached là một kho lưu trữ khóa-giá trị dựa trên bộ nhớ cho các phần nhỏ dữ liệu. Nó hỗ trợ bộ nhớ đệm phân tán trên nhiều máy chủ.
  2. Cơ sở dữ liệu : Ở đây, các đoạn bộ nhớ cache được lưu trữ trong một cơ sở dữ liệu. Một bảng cho mục đích đó có thể được tạo bằng một trong các lệnh quản trị của Django. Đây không phải là loại bộ nhớ đệm hiệu quả nhất, nhưng nó có thể hữu ích để lưu trữ các truy vấn cơ sở dữ liệu phức tạp.
  3. Hệ thống tệp : Bộ nhớ cache được lưu trên hệ thống tệp, trong các tệp riêng biệt cho mỗi giá trị bộ nhớ cache. Đây là loại chậm nhất trong số các loại bộ nhớ đệm, nhưng lại là loại dễ thiết lập nhất trong môi trường sản xuất.
  4. Bộ nhớ cục bộ : Bộ nhớ đệm cục bộ, phù hợp nhất cho môi trường thử nghiệm hoặc phát triển cục bộ của bạn. Mặc dù nó nhanh gần như Memcached, nhưng nó không thể mở rộng ra ngoài một máy chủ duy nhất, vì vậy không thích hợp để sử dụng làm bộ nhớ cache dữ liệu cho bất kỳ ứng dụng nào sử dụng nhiều hơn một máy chủ web.
  5. Dummy : Một bộ đệm "giả" không thực sự lưu vào bộ đệm bất kỳ thứ gì nhưng vẫn thực hiện giao diện bộ đệm. Nó có nghĩa là được sử dụng trong phát triển hoặc thử nghiệm khi bạn không muốn lưu vào bộ nhớ đệm nhưng không muốn thay đổi mã của mình.

Các cấp độ bộ nhớ đệm Django

Bộ nhớ đệm trong Django có thể được thực hiện ở các cấp độ khác nhau (hoặc các phần của trang web). Bạn có thể lưu vào bộ nhớ cache toàn bộ trang web hoặc các phần cụ thể với nhiều mức độ chi tiết khác nhau (được liệt kê theo thứ tự mức độ chi tiết giảm dần):

Bộ nhớ cache của mỗi trang web

Đây là cách dễ nhất để triển khai bộ nhớ đệm trong Django. Để làm điều này, tất cả những gì bạn phải làm là thêm hai lớp phần mềm trung gian vào tệp settings.py của bạn :

MIDDLEWARE = [
    'django.middleware.cache.UpdateCacheMiddleware',     # NEW
    'django.middleware.common.CommonMiddleware',
    'django.middleware.cache.FetchFromCacheMiddleware',  # NEW
]

Thứ tự của phần mềm trung gian là quan trọng ở đây. UpdateCacheMiddlewarephải đến trước FetchFromCacheMiddleware. Để biết thêm thông tin, hãy xem Thứ tự của phần mềm MIDDLEWARE từ tài liệu Django.

Sau đó, bạn cần thêm các cài đặt sau:

CACHE_MIDDLEWARE_ALIAS = 'default'  # which cache alias to use
CACHE_MIDDLEWARE_SECONDS = '600'    # number of seconds to cache a page for (TTL)
CACHE_MIDDLEWARE_KEY_PREFIX = ''    # should be used if the cache is shared across multiple sites that use the same Django instance

Mặc dù bộ nhớ đệm toàn bộ trang web có thể là một lựa chọn tốt nếu trang web của bạn có ít hoặc không có nội dung động, nhưng nó có thể không thích hợp để sử dụng cho các trang web lớn với phần mềm phụ trợ bộ nhớ đệm dựa trên bộ nhớ vì RAM đắt.

Bộ nhớ đệm cho mỗi lần xem

Thay vì lãng phí không gian bộ nhớ quý giá vào bộ nhớ đệm các trang tĩnh hoặc trang động lấy dữ liệu từ một API thay đổi nhanh chóng, bạn có thể lưu vào bộ nhớ cache các chế độ xem cụ thể. Đây là cách tiếp cận mà chúng tôi sẽ sử dụng trong bài viết này. Đây cũng là cấp bộ nhớ đệm mà bạn hầu như luôn phải bắt đầu khi tìm cách triển khai bộ nhớ đệm trong ứng dụng Django của mình.

Bạn có thể triển khai loại bộ nhớ cache này với bộ trang trí cache_page trên hàm xem trực tiếp hoặc trong đường dẫn bên trong URLConf:

from django.views.decorators.cache import cache_page

@cache_page(60 * 15)
def your_view(request):
    ...

# or

from django.views.decorators.cache import cache_page

urlpatterns = [
    path('object/<int:object_id>/', cache_page(60 * 15)(your_view)),
]

Bản thân bộ nhớ đệm dựa trên URL, vì vậy, các yêu cầu, ví dụ, object/1object/2sẽ được lưu vào bộ nhớ cache riêng.

Cần lưu ý rằng việc triển khai bộ nhớ cache trực tiếp trên chế độ xem làm cho việc vô hiệu hóa bộ nhớ cache trong một số trường hợp nhất định trở nên khó khăn hơn. Ví dụ: điều gì sẽ xảy ra nếu bạn muốn cho phép một số người dùng nhất định truy cập vào chế độ xem mà không cần bộ nhớ cache? Kích hoạt bộ nhớ cache thông qua URLConfcung cấp cơ hội liên kết một URL khác với chế độ xem không sử dụng bộ nhớ cache:

from django.views.decorators.cache import cache_page

urlpatterns = [
    path('object/<int:object_id>/', your_view),
    path('object/cache/<int:object_id>/', cache_page(60 * 15)(your_view)),
]

Bộ nhớ cache phân đoạn mẫu

Nếu các mẫu của bạn chứa các phần thường xuyên thay đổi dựa trên dữ liệu, bạn có thể muốn loại bỏ chúng khỏi bộ nhớ cache.

Ví dụ: có thể bạn sử dụng email của người dùng đã xác thực trong thanh điều hướng trong một khu vực của mẫu. Chà, Nếu bạn có hàng nghìn người dùng thì phân đoạn đó sẽ được nhân đôi hàng nghìn lần trong RAM, mỗi người dùng một lần. Đây là lúc bộ nhớ đệm phân đoạn mẫu phát huy tác dụng, cho phép bạn chỉ định các khu vực cụ thể của mẫu để lưu vào bộ nhớ đệm.

Để lưu vào bộ nhớ cache một danh sách các đối tượng:

{% load cache %}

{% cache 500 object_list %}
  <ul>
    {% for object in objects %}
      <li>{{ object.title }}</li>
    {% endfor %}
  </ul>
{% endcache %}

Tại đây, {% load cache %}cung cấp cho chúng tôi quyền truy cập vào cachethẻ mẫu, thẻ này dự kiến ​​thời gian chờ bộ nhớ cache tính bằng giây ( 500) cùng với tên của phân đoạn bộ nhớ cache ( object_list).

API bộ nhớ cache cấp thấp

Đối với trường hợp các tùy chọn trước đó không cung cấp đủ chi tiết, bạn có thể sử dụng API cấp thấp để quản lý các đối tượng riêng lẻ trong bộ đệm bằng khóa bộ đệm.

Ví dụ:

from django.core.cache import cache


def get_context_data(self, **kwargs):
    context = super().get_context_data(**kwargs)

    objects = cache.get('objects')

    if objects is None:
        objects = Objects.all()
        cache.set('objects', objects)

    context['objects'] = objects

    return context

Trong ví dụ này, bạn sẽ muốn vô hiệu hóa (hoặc xóa) bộ đệm khi các đối tượng được thêm, thay đổi hoặc xóa khỏi cơ sở dữ liệu. Một cách để quản lý điều này là thông qua các tín hiệu cơ sở dữ liệu:

from django.core.cache import cache
from django.db.models.signals import post_delete, post_save
from django.dispatch import receiver


@receiver(post_delete, sender=Object)
def object_post_delete_handler(sender, **kwargs):
     cache.delete('objects')


@receiver(post_save, sender=Object)
def object_post_save_handler(sender, **kwargs):
    cache.delete('objects')

Để biết thêm về cách sử dụng tín hiệu cơ sở dữ liệu để làm mất hiệu lực bộ nhớ cache, hãy xem API bộ nhớ cache cấp thấp trong bài viết Django .

Cùng với đó, hãy xem một số ví dụ.

Thiết lập dự án

Sao chép dự án cơ sở từ repo cache-django-view , sau đó kiểm tra nhánh cơ sở:

$ git clone https://github.com/testdrivenio/cache-django-view.git --branch base --single-branch
$ cd cache-django-view

Tạo (và kích hoạt) một môi trường ảo và cài đặt các yêu cầu:

$ python3.9 -m venv venv
$ source venv/bin/activate
(venv)$ pip install -r requirements.txt

Áp dụng di chuyển Django, sau đó khởi động máy chủ:

(venv)$ python manage.py migrate
(venv)$ python manage.py runserver

Điều hướng đến http://127.0.0.1:8000 trong trình duyệt bạn chọn để đảm bảo rằng mọi thứ hoạt động như mong đợi.

Bạn nên thấy:

trang web chưa được lưu trữ

Ghi lại thiết bị đầu cuối của bạn. Bạn sẽ thấy tổng thời gian thực hiện yêu cầu:

Total time: 2.23s

Số liệu này đến từ core / middleware.py :

import logging
import time


def metric_middleware(get_response):
    def middleware(request):
        # Get beginning stats
        start_time = time.perf_counter()

        # Process the request
        response = get_response(request)

        # Get ending stats
        end_time = time.perf_counter()

        # Calculate stats
        total_time = end_time - start_time

        # Log the results
        logger = logging.getLogger('debug')
        logger.info(f'Total time: {(total_time):.2f}s')
        print(f'Total time: {(total_time):.2f}s')

        return response

    return middleware

Xem nhanh chế độ xem trong apicalls / views.py :

import datetime

import requests
from django.views.generic import TemplateView


BASE_URL = 'https://httpbin.org/'


class ApiCalls(TemplateView):
    template_name = 'apicalls/home.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        response = requests.get(f'{BASE_URL}/delay/2')
        response.raise_for_status()
        context['content'] = 'Results received!'
        context['current_time'] = datetime.datetime.now()
        return context

Chế độ xem này thực hiện một cuộc gọi HTTP requeststới httpbin.org . Để mô phỏng một yêu cầu dài, phản hồi từ API bị trì hoãn trong hai giây. Vì vậy, sẽ mất khoảng hai giây để http://127.0.0.1:8000 hiển thị không chỉ theo yêu cầu ban đầu mà còn cho từng yêu cầu tiếp theo. Mặc dù tải hai giây có phần chấp nhận được đối với yêu cầu ban đầu, nó hoàn toàn không được chấp nhận đối với các yêu cầu tiếp theo vì dữ liệu không thay đổi. Hãy khắc phục điều này bằng cách lưu vào bộ nhớ đệm toàn bộ chế độ xem bằng cách sử dụng mức bộ nhớ cache Per-view của Django.

Quy trình làm việc:

  1. Thực hiện cuộc gọi HTTP đầy đủ tới httpbin.org theo yêu cầu ban đầu
  2. Lưu vào bộ nhớ cache của chế độ xem
  3. Các yêu cầu tiếp theo sau đó sẽ kéo từ bộ nhớ cache, bỏ qua lệnh gọi HTTP
  4. Làm mất hiệu lực bộ nhớ cache sau một khoảng thời gian (TTL)

Điểm chuẩn hiệu suất cơ bản

Trước khi thêm bộ nhớ cache, hãy nhanh chóng chạy kiểm tra tải để lấy đường cơ sở điểm chuẩn bằng Apache Bench , để biết sơ bộ về số lượng yêu cầu mà ứng dụng của chúng tôi có thể xử lý mỗi giây.

Apache Bench được cài đặt sẵn trên Mac.

Nếu bạn đang sử dụng hệ thống Linux, rất có thể nó đã được cài đặt và sẵn sàng hoạt động. Nếu không, bạn có thể cài đặt qua APT ( apt-get install apache2-utils) hoặc YUM ( yum install httpd-tools).

Người dùng Windows sẽ cần tải xuống và giải nén tệp nhị phân Apache.

Thêm Gunicorn vào tệp yêu cầu:

gunicorn==20.1.0

Giết máy chủ nhà phát triển Django và cài đặt Gunicorn:

(venv)$ pip install -r requirements.txt

Tiếp theo, cung cấp ứng dụng Django với Gunicorn (và bốn nhân viên ) như vậy:

(venv)$ gunicorn core.wsgi:application -w 4

Trong một cửa sổ đầu cuối mới, chạy Apache Bench:

$ ab -n 100 -c 10 http://127.0.0.1:8000/

Điều này sẽ mô phỏng 100 kết nối trên 10 luồng đồng thời. Đó là 100 yêu cầu, 10 yêu cầu cùng một lúc.

Ghi lại các yêu cầu mỗi giây:

Requests per second:    1.69 [#/sec] (mean)

Hãy nhớ rằng Thanh công cụ gỡ lỗi Django sẽ thêm một chút chi phí. Điểm chuẩn nói chung là khó có thể hoàn toàn đúng. Điều quan trọng là tính nhất quán. Chọn một chỉ số để tập trung vào và sử dụng cùng một môi trường cho mỗi bài kiểm tra.

Giết máy chủ Gunicorn và sao lưu máy chủ Django dev:

(venv)$ python manage.py runserver

Cùng với đó, hãy xem cách lưu vào bộ nhớ cache một chế độ xem.

Lưu vào bộ nhớ cache một lượt xem

Bắt đầu bằng cách trang trí ApiCallskhung nhìn với trình @cache_pagetrang trí như sau:

import datetime

import requests
from django.utils.decorators import method_decorator # NEW
from django.views.decorators.cache import cache_page # NEW
from django.views.generic import TemplateView


BASE_URL = 'https://httpbin.org/'


@method_decorator(cache_page(60 * 5), name='dispatch') # NEW
class ApiCalls(TemplateView):
    template_name = 'apicalls/home.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        response = requests.get(f'{BASE_URL}/delay/2')
        response.raise_for_status()
        context['content'] = 'Results received!'
        context['current_time'] = datetime.datetime.now()
        return context

Vì chúng ta đang sử dụng dạng xem dựa trên lớp, chúng ta không thể đặt trình trang trí trực tiếp trên lớp, vì vậy chúng ta đã sử dụng a method_decoratorvà chỉ định dispatch(làm phương thức được trang trí) cho đối số tên.

Bộ nhớ cache trong ví dụ này đặt thời gian chờ (hoặc TTL) là năm phút.

Ngoài ra, bạn có thể đặt điều này trong cài đặt của mình như sau:

# Cache time to live is 5 minutes
CACHE_TTL = 60 * 5

Sau đó, quay lại chế độ xem:

import datetime

import requests
from django.conf import settings
from django.core.cache.backends.base import DEFAULT_TIMEOUT
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page
from django.views.generic import TemplateView

BASE_URL = 'https://httpbin.org/'
CACHE_TTL = getattr(settings, 'CACHE_TTL', DEFAULT_TIMEOUT)


@method_decorator(cache_page(CACHE_TTL), name='dispatch')
class ApiCalls(TemplateView):
    template_name = 'apicalls/home.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        response = requests.get(f'{BASE_URL}/delay/2')
        response.raise_for_status()
        context['content'] = 'Results received!'
        context['current_time'] = datetime.datetime.now()
        return context

Tiếp theo, hãy thêm một phụ trợ bộ nhớ cache.

Redis vs Memcached

MemcachedRedis là các kho lưu trữ dữ liệu giá trị, khóa trong bộ nhớ. Chúng dễ sử dụng và được tối ưu hóa để tra cứu hiệu suất cao. Bạn có thể sẽ không thấy nhiều sự khác biệt về hiệu suất hoặc mức sử dụng bộ nhớ giữa hai thiết bị này. Điều đó nói rằng, Memcached dễ cấu hình hơn một chút vì nó được thiết kế để đơn giản và dễ sử dụng. Mặt khác, Redis có bộ tính năng phong phú hơn nên nó có nhiều trường hợp sử dụng ngoài bộ nhớ đệm. Ví dụ: nó thường được sử dụng để lưu trữ các phiên người dùng hoặc làm môi giới thông báo trong hệ thống pub / sub. Vì tính linh hoạt của nó, trừ khi bạn đã đầu tư vào Memcached, Redis là giải pháp tốt hơn nhiều.

Để biết thêm về điều này, hãy xem lại câu trả lời Stack Overflow này.

Tiếp theo, chọn kho lưu trữ dữ liệu của bạn và hãy xem cách lưu vào bộ nhớ cache một chế độ xem.

Tùy chọn 1: Redis với Django

Tải xuống và cài đặt Redis.

Nếu bạn đang sử dụng máy Mac, chúng tôi khuyên bạn nên cài đặt Redis với Homebrew :

$ brew install redis

Sau khi được cài đặt, trong một cửa sổ đầu cuối mới, hãy khởi động máy chủ Redis và đảm bảo rằng nó đang chạy trên cổng mặc định của nó, 6379. Số cổng sẽ rất quan trọng khi chúng tôi cho Django biết cách giao tiếp với Redis.

$ redis-server

Để Django sử dụng Redis làm phụ trợ bộ nhớ cache, trước tiên chúng ta cần cài đặt django-redis .

Thêm nó vào tệp tin request.txt :

django-redis==5.0.0

Cài đặt:

(venv)$ pip install -r requirements.txt

Tiếp theo, thêm phần phụ trợ tùy chỉnh vào tệp settings.py :

CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': 'redis://127.0.0.1:6379/1',
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient',
        }
    }
}

Bây giờ, khi bạn chạy lại máy chủ, Redis sẽ được sử dụng làm phụ trợ bộ nhớ cache:

(venv)$ python manage.py runserver

Khi máy chủ được thiết lập và đang chạy, hãy điều hướng đến http://127.0.0.1:8000 .

Yêu cầu đầu tiên vẫn sẽ mất khoảng hai giây. Làm mới trang. Trang sẽ tải gần như ngay lập tức. Hãy xem thời gian tải trong thiết bị đầu cuối của bạn. Nó phải gần bằng 0:

Total time: 0.01s

Tò mò dữ liệu được lưu trong bộ nhớ cache trông như thế nào bên trong Redis?

Chạy Redis CLI ở chế độ tương tác trong một cửa sổ đầu cuối mới:

$ redis-cli

Bạn nên thấy:

127.0.0.1:6379>

Chạy pingđể đảm bảo mọi thứ hoạt động bình thường:

127.0.0.1:6379> ping
PONG

Quay lại tệp cài đặt. Chúng tôi đã sử dụng cơ sở dữ liệu Redis số 1 'LOCATION': 'redis://127.0.0.1:6379/1',:. Vì vậy, hãy chạy select 1để chọn cơ sở dữ liệu đó và sau đó chạy keys *để xem tất cả các khóa:

127.0.0.1:6379> select 1
OK

127.0.0.1:6379[1]> keys *
1) ":1:views.decorators.cache.cache_header..17abf5259517d604cc9599a00b7385d6.en-us.UTC"
2) ":1:views.decorators.cache.cache_page..GET.17abf5259517d604cc9599a00b7385d6.d41d8cd98f00b204e9800998ecf8427e.en-us.UTC"

Chúng ta có thể thấy rằng Django đã đưa vào một khóa tiêu đề và một cache_pagekhóa chính.

Để xem dữ liệu thực được lưu trong bộ nhớ cache, hãy chạy getlệnh với khóa làm đối số:

127.0.0.1:6379[1]> get ":1:views.decorators.cache.cache_page..GET.17abf5259517d604cc9599a00b7385d6.d41d8cd98f00b204e9800998ecf8427e.en-us.UTC"

Của bạn sẽ thấy một cái gì đó tương tự như:

"\x80\x05\x95D\x04\x00\x00\x00\x00\x00\x00\x8c\x18django.template.response\x94\x8c\x10TemplateResponse
\x94\x93\x94)\x81\x94}\x94(\x8c\x05using\x94N\x8c\b_headers\x94}\x94(\x8c\x0ccontent-type\x94\x8c\
x0cContent-Type\x94\x8c\x18text/html; charset=utf-8\x94\x86\x94\x8c\aexpires\x94\x8c\aExpires\x94\x8c\x1d
Fri, 01 May 2020 13:36:59 GMT\x94\x86\x94\x8c\rcache-control\x94\x8c\rCache-Control\x94\x8c\x0
bmax-age=300\x94\x86\x94u\x8c\x11_resource_closers\x94]\x94\x8c\x0e_handler_class\x94N\x8c\acookies
\x94\x8c\x0chttp.cookies\x94\x8c\x0cSimpleCookie\x94\x93\x94)\x81\x94\x8c\x06closed\x94\x89\x8c
\x0e_reason_phrase\x94N\x8c\b_charset\x94N\x8c\n_container\x94]\x94B\xaf\x02\x00\x00
<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Home</title>\n
<link rel=\"stylesheet\" href=\"https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css\
"\n          integrity=\"sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh\"
crossorigin=\"anonymous\">\n\n</head>\n<body>\n<div class=\"container\">\n    <div class=\"pt-3\">\n
<h1>Below is the result of the APICall</h1>\n    </div>\n    <div class=\"pt-3 pb-3\">\n
<a href=\"/\">\n            <button type=\"button\" class=\"btn btn-success\">\n
Get new data\n            </button>\n        </a>\n    </div>\n    Results received!<br>\n
13:31:59\n</div>\n</body>\n</html>\x94a\x8c\x0c_is_rendered\x94\x88ub."

Thoát CLI tương tác sau khi hoàn tất:

127.0.0.1:6379[1]> exit

Bỏ qua phần "Kiểm tra Hiệu suất".

Tùy chọn 2: Đã ghi nhớ với Django

Bắt đầu bằng cách thêm pymemcache vào tệp tin request.txt :

pymemcache==3.5.0

Cài đặt các phụ thuộc:

(venv)$ pip install -r requirements.txt

Tiếp theo, chúng ta cần cập nhật cài đặt trong core / settings.py để bật phụ trợ Memcached:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
        'LOCATION': '127.0.0.1:11211',
    }
}

Ở đây, chúng tôi đã thêm phần phụ trợ PyMemcacheCache và chỉ ra rằng Memcached sẽ chạy trên máy cục bộ của chúng tôi trên localhost (127.0.0.1) cổng 11211, là cổng mặc định cho Memcached.

Tiếp theo, chúng ta cần cài đặt và chạy trình nền Memcached. Cách dễ nhất để cài đặt nó, là thông qua trình quản lý gói như APT, YUM, Homebrew hoặc Chocolatey tùy thuộc vào hệ điều hành của bạn:

# linux
$ apt-get install memcached
$ yum install memcached

# mac
$ brew install memcached

# windows
$ choco install memcached

Sau đó, chạy nó trong một thiết bị đầu cuối khác trên cổng 11211:

$ memcached -p 11211

# test: telnet localhost 11211

Để biết thêm thông tin về cài đặt và cấu hình Memcached, hãy xem lại wiki chính thức .

Điều hướng lại đến http://127.0.0.1:8000 trong trình duyệt của chúng tôi. Yêu cầu đầu tiên vẫn sẽ mất đủ hai giây, nhưng tất cả các yêu cầu tiếp theo sẽ tận dụng bộ nhớ cache. Vì vậy, nếu bạn làm mới hoặc nhấn nút "Nhận dữ liệu mới", trang sẽ tải gần như ngay lập tức.

Thời gian thực hiện trong thiết bị đầu cuối của bạn như thế nào?

Total time: 0.03s

Kiểm tra hiệu năng

Nếu chúng ta xem xét thời gian cần để tải yêu cầu đầu tiên so với yêu cầu thứ hai (được lưu trong bộ nhớ cache) trong Thanh công cụ gỡ lỗi Django, nó sẽ giống như:

thời gian tải chưa được lưu trữ

thời gian tải với bộ nhớ cache

Cũng trong Thanh công cụ gỡ lỗi, bạn có thể xem các hoạt động trong bộ nhớ cache:

hoạt động bộ nhớ cache

Quay Gunicorn sao lưu một lần nữa và chạy lại các bài kiểm tra hiệu suất:

$ ab -n 100 -c 10 http://127.0.0.1:8000/

Yêu cầu mới mỗi giây là gì? Đó là khoảng 36 trên máy của tôi!

Sự kết luận

Trong bài viết này, chúng tôi đã xem xét các tùy chọn tích hợp sẵn khác nhau cho bộ nhớ đệm trong Django cũng như các mức bộ nhớ đệm khác nhau có sẵn. Chúng tôi cũng trình bày chi tiết cách lưu vào bộ nhớ cache một chế độ xem bằng cách sử dụng bộ đệm ẩn Per-view của Django với cả Memcached và Redis.

Bạn có thể tìm thấy mã cuối cùng cho cả hai tùy chọn, Memcached và Redis, trong kho lưu trữ bộ nhớ cache-django-view .

--

Nói chung, bạn sẽ muốn xem xét bộ nhớ đệm khi kết xuất trang chậm do độ trễ mạng từ các truy vấn cơ sở dữ liệu hoặc cuộc gọi HTTP.

Từ đó, bạn nên sử dụng chương trình phụ trợ bộ nhớ cache Django tùy chỉnh với Redis với một Per-viewloại. Nếu bạn cần kiểm soát và chi tiết hơn, vì không phải tất cả dữ liệu trên mẫu đều giống nhau đối với tất cả người dùng hoặc các phần của dữ liệu thay đổi thường xuyên, thì hãy chuyển xuống bộ nhớ cache phân đoạn Mẫu hoặc API bộ nhớ cache cấp thấp.

Nguồn:  https://testdriven.io

#django #caching 

Tìm Hiểu Về Bộ Nhớ Đệm Trong Django

Узнайте о кэшировании в Django

Кэширование обычно является наиболее эффективным способом повышения производительности приложения.

Для динамических веб-сайтов при отображении шаблона вам часто приходится собирать данные из различных источников (например, базы данных, файловой системы и сторонних API), обрабатывать данные и применять бизнес-логику к перед подачей клиенту. Любая задержка из-за сетевой задержки будет замечена конечным пользователем.

Например, предположим, что вам нужно сделать HTTP-запрос к внешнему API, чтобы получить данные, необходимые для отображения шаблона. Даже в идеальных условиях это увеличит время рендеринга, что увеличит общее время загрузки. Что, если API выйдет из строя или, может быть, вы подпадаете под ограничение скорости? В любом случае, если данные обновляются нечасто, рекомендуется реализовать механизм кэширования, чтобы избежать необходимости выполнять вызов HTTP в целом для каждого клиентского запроса.

В этой статье мы рассмотрим, как это сделать, сначала рассмотрев структуру кэширования Django в целом, а затем подробно описав, как кэшировать представление Django.

Зависимости:

  1. Джанго v3.2.5
  2. Джанго-Редис v5.0.0
  3. Питон v3.9.6
  4. pymemcache v3.5.0
  5. Запросы v2.26.0

Цели

К концу этого урока вы должны уметь:

  1. Объясните, почему вы можете рассмотреть возможность кэширования представления Django.
  2. Описать встроенные опции Django, доступные для кэширования.
  3. Кэшировать представление Django с помощью Redis
  4. Нагрузочное тестирование приложения Django с помощью Apache Bench
  5. Кэшируйте представление Django с помощью Memcached

Типы кэширования Django

Django поставляется с несколькими встроенными бэкэндами для кэширования, а также поддерживает пользовательский бэкенд.

Встроенные опции:

  1. Memcached : Memcached — это хранилище ключей и значений в памяти для небольших фрагментов данных. Он поддерживает распределенное кэширование на нескольких серверах.
  2. База данных : здесь фрагменты кеша хранятся в базе данных. Таблицу для этой цели можно создать с помощью одной из команд администратора Django. Это не самый производительный тип кэширования, но он может быть полезен для хранения сложных запросов к базе данных.
  3. Файловая система : кеш сохраняется в файловой системе в отдельных файлах для каждого значения кеша. Это самый медленный из всех типов кэширования, но его проще всего настроить в производственной среде.
  4. Локальная память : кэш локальной памяти, который лучше всего подходит для вашей локальной среды разработки или тестирования. Хотя он почти такой же быстрый, как Memcached, он не может масштабироваться за пределы одного сервера, поэтому его нецелесообразно использовать в качестве кеша данных для любого приложения, использующего более одного веб-сервера.
  5. Dummy : «фиктивный» кеш, который на самом деле ничего не кеширует, но все же реализует интерфейс кеша. Он предназначен для использования в разработке или тестировании, когда вам не нужно кэширование, но вы не хотите изменять свой код.

Уровни кэширования Django

Кэширование в Django может быть реализовано на разных уровнях (или частях сайта). Вы можете кэшировать весь сайт или его отдельные части с различными уровнями детализации (перечислены в порядке убывания детализации):

Кэш для каждого сайта

Это самый простой способ реализовать кеширование в Django. Для этого все, что вам нужно сделать, это добавить два класса промежуточного программного обеспечения в ваш файл settings.py :

MIDDLEWARE = [
    'django.middleware.cache.UpdateCacheMiddleware',     # NEW
    'django.middleware.common.CommonMiddleware',
    'django.middleware.cache.FetchFromCacheMiddleware',  # NEW
]

Здесь важен порядок промежуточного программного обеспечения. UpdateCacheMiddlewareдолжен прийти раньше FetchFromCacheMiddleware. Для получения дополнительной информации взгляните на Order of MIDDLEWARE из документации Django.

Затем вам нужно добавить следующие настройки:

CACHE_MIDDLEWARE_ALIAS = 'default'  # which cache alias to use
CACHE_MIDDLEWARE_SECONDS = '600'    # number of seconds to cache a page for (TTL)
CACHE_MIDDLEWARE_KEY_PREFIX = ''    # should be used if the cache is shared across multiple sites that use the same Django instance

Хотя кэширование всего сайта может быть хорошим вариантом, если на вашем сайте мало или совсем нет динамического контента, это может быть нецелесообразно для больших сайтов с кеш-сервером на основе памяти, поскольку оперативная память стоит дорого.

Кэш для просмотра

Вместо того, чтобы тратить драгоценное пространство памяти на кэширование статических страниц или динамических страниц, которые получают данные из быстро меняющегося API, вы можете кэшировать определенные представления. Именно этот подход мы будем использовать в этой статье. Это также уровень кэширования, с которого вы почти всегда должны начинать, когда хотите реализовать кэширование в своем приложении Django.

Вы можете реализовать этот тип кеша с помощью декоратора cache_page либо непосредственно в функции представления, либо по пути внутри URLConf:

from django.views.decorators.cache import cache_page

@cache_page(60 * 15)
def your_view(request):
    ...

# or

from django.views.decorators.cache import cache_page

urlpatterns = [
    path('object/<int:object_id>/', cache_page(60 * 15)(your_view)),
]

Сам кеш основан на URL, поэтому запросы к, скажем, object/1и object/2будут кешироваться отдельно.

Стоит отметить, что реализация кеша непосредственно в представлении усложняет отключение кеша в определенных ситуациях. Например, что если вы хотите разрешить определенным пользователям доступ к представлению без кеша? Включение кеша через the URLConfдает возможность связать другой URL с представлением, которое не использует кеш:

from django.views.decorators.cache import cache_page

urlpatterns = [
    path('object/<int:object_id>/', your_view),
    path('object/cache/<int:object_id>/', cache_page(60 * 15)(your_view)),
]

Кэш фрагментов шаблона

Если ваши шаблоны содержат части, которые часто изменяются в зависимости от данных, вы, вероятно, захотите оставить их вне кеша.

Например, возможно, вы используете адрес электронной почты аутентифицированного пользователя на панели навигации в области шаблона. Что ж, если у вас есть тысячи пользователей, то этот фрагмент будет продублирован в оперативной памяти тысячи раз, по одному для каждого пользователя. Именно здесь вступает в действие кэширование фрагментов шаблона, которое позволяет указать определенные области шаблона для кэширования.

Чтобы кэшировать список объектов:

{% load cache %}

{% cache 500 object_list %}
  <ul>
    {% for object in objects %}
      <li>{{ object.title }}</li>
    {% endfor %}
  </ul>
{% endcache %}

Здесь {% load cache %}дает нам доступ к cacheтегу шаблона, который ожидает тайм-аут кеша в секундах ( 500) вместе с именем фрагмента кеша ( object_list).

API низкоуровневого кеша

В случаях, когда предыдущие варианты не обеспечивают достаточной детализации, вы можете использовать низкоуровневый API для управления отдельными объектами в кеше по ключу кеша.

Например:

from django.core.cache import cache


def get_context_data(self, **kwargs):
    context = super().get_context_data(**kwargs)

    objects = cache.get('objects')

    if objects is None:
        objects = Objects.all()
        cache.set('objects', objects)

    context['objects'] = objects

    return context

В этом примере вы захотите аннулировать (или удалить) кэш при добавлении, изменении или удалении объектов из базы данных. Одним из способов управления этим является использование сигналов базы данных:

from django.core.cache import cache
from django.db.models.signals import post_delete, post_save
from django.dispatch import receiver


@receiver(post_delete, sender=Object)
def object_post_delete_handler(sender, **kwargs):
     cache.delete('objects')


@receiver(post_save, sender=Object)
def object_post_save_handler(sender, **kwargs):
    cache.delete('objects')

Чтобы узнать больше об использовании сигналов базы данных для аннулирования кеша, ознакомьтесь со статьей Low-Level Cache API в статье Django.

При этом давайте рассмотрим несколько примеров.

Настройка проекта

Клонируйте базовый проект из репозитория cache-django-view , а затем проверьте базовую ветку:

$ git clone https://github.com/testdrivenio/cache-django-view.git --branch base --single-branch
$ cd cache-django-view

Создайте (и активируйте) виртуальную среду и установите требования:

$ python3.9 -m venv venv
$ source venv/bin/activate
(venv)$ pip install -r requirements.txt

Примените миграции Django, а затем запустите сервер:

(venv)$ python manage.py migrate
(venv)$ python manage.py runserver

Перейдите по адресу http://127.0.0.1:8000 в выбранном вами браузере, чтобы убедиться, что все работает должным образом.

Тебе следует увидеть:

некешированная веб-страница

Обратите внимание на свой терминал. Вы должны увидеть общее время выполнения запроса:

Total time: 2.23s

Эта метрика взята из core/middleware.py :

import logging
import time


def metric_middleware(get_response):
    def middleware(request):
        # Get beginning stats
        start_time = time.perf_counter()

        # Process the request
        response = get_response(request)

        # Get ending stats
        end_time = time.perf_counter()

        # Calculate stats
        total_time = end_time - start_time

        # Log the results
        logger = logging.getLogger('debug')
        logger.info(f'Total time: {(total_time):.2f}s')
        print(f'Total time: {(total_time):.2f}s')

        return response

    return middleware

Взгляните на представление в apicalls/views.py :

import datetime

import requests
from django.views.generic import TemplateView


BASE_URL = 'https://httpbin.org/'


class ApiCalls(TemplateView):
    template_name = 'apicalls/home.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        response = requests.get(f'{BASE_URL}/delay/2')
        response.raise_for_status()
        context['content'] = 'Results received!'
        context['current_time'] = datetime.datetime.now()
        return context

Это представление выполняет HTTP-вызов requestsс httpbin.org . Для имитации длинного запроса ответ от API задерживается на две секунды. Таким образом, http://127.0.0.1:8000 должно занять около двух секунд , чтобы отобразить не только первоначальный запрос, но и каждый последующий запрос. В то время как двухсекундная загрузка в некоторой степени приемлема для первоначального запроса, она совершенно неприемлема для последующих запросов, поскольку данные не меняются. Давайте исправим это, кэшируя все представление, используя уровень кэширования Django для каждого представления.

Рабочий процесс:

  1. Сделать полный HTTP-вызов httpbin.org по первоначальному запросу
  2. Кэшировать представление
  3. Последующие запросы будут извлекаться из кеша в обход HTTP-вызова.
  4. Сделать кеш недействительным через определенный период времени (TTL)

Базовый показатель производительности

Прежде чем добавлять кеш, давайте быстро запустим нагрузочный тест, чтобы получить базовый уровень производительности с помощью Apache Bench , чтобы получить приблизительное представление о том, сколько запросов наше приложение может обрабатывать в секунду.

Apache Bench предустановлен на Mac.

Если вы работаете в системе Linux, скорее всего, она уже установлена ​​и готова к работе. Если нет, вы можете установить через APT ( apt-get install apache2-utils) или YUM ( yum install httpd-tools).

Пользователям Windows потребуется загрузить и извлечь двоичные файлы Apache.

Добавьте Gunicorn в файл требований:

gunicorn==20.1.0

Убейте сервер разработки Django и установите Gunicorn:

(venv)$ pip install -r requirements.txt

Затем запустите приложение Django с помощью Gunicorn (и четырех рабочих процессов ) следующим образом:

(venv)$ gunicorn core.wsgi:application -w 4

В новом окне терминала запустите Apache Bench:

$ ab -n 100 -c 10 http://127.0.0.1:8000/

Это будет имитировать 100 соединений по 10 одновременным потокам. Это 100 запросов, по 10 за раз.

Обратите внимание на количество запросов в секунду:

Requests per second:    1.69 [#/sec] (mean)

Имейте в виду, что панель инструментов Django Debug добавит немного накладных расходов. Сравнительный анализ вообще трудно провести идеально правильно. Главное — постоянство. Выберите метрику, на которой следует сосредоточиться, и используйте одну и ту же среду для каждого теста.

Убейте сервер Gunicorn и запустите резервную копию сервера Django dev:

(venv)$ python manage.py runserver

Теперь давайте посмотрим, как кэшировать представление.

Кэширование представления

Начните с украшения ApiCallsпредставления с помощью @cache_pageдекоратора следующим образом:

import datetime

import requests
from django.utils.decorators import method_decorator # NEW
from django.views.decorators.cache import cache_page # NEW
from django.views.generic import TemplateView


BASE_URL = 'https://httpbin.org/'


@method_decorator(cache_page(60 * 5), name='dispatch') # NEW
class ApiCalls(TemplateView):
    template_name = 'apicalls/home.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        response = requests.get(f'{BASE_URL}/delay/2')
        response.raise_for_status()
        context['content'] = 'Results received!'
        context['current_time'] = datetime.datetime.now()
        return context

Поскольку мы используем представление на основе классов, мы не можем поместить декоратор непосредственно в класс, поэтому мы использовали method_decoratorи указали dispatch(в качестве метода, который нужно декорировать) для аргумента имени.

Кэш в этом примере устанавливает время ожидания (или TTL) в пять минут.

Кроме того, вы можете установить это в своих настройках следующим образом:

# Cache time to live is 5 minutes
CACHE_TTL = 60 * 5

Затем снова в представлении:

import datetime

import requests
from django.conf import settings
from django.core.cache.backends.base import DEFAULT_TIMEOUT
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page
from django.views.generic import TemplateView

BASE_URL = 'https://httpbin.org/'
CACHE_TTL = getattr(settings, 'CACHE_TTL', DEFAULT_TIMEOUT)


@method_decorator(cache_page(CACHE_TTL), name='dispatch')
class ApiCalls(TemplateView):
    template_name = 'apicalls/home.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        response = requests.get(f'{BASE_URL}/delay/2')
        response.raise_for_status()
        context['content'] = 'Results received!'
        context['current_time'] = datetime.datetime.now()
        return context

Далее давайте добавим кеш-бэкенд.

Redis против Memcached

Memcached и Redis — это хранилища данных «ключ-значение» в памяти. Они просты в использовании и оптимизированы для высокопроизводительного поиска. Вы, вероятно, не увидите большой разницы в производительности или использовании памяти между ними. Тем не менее, Memcached немного проще настроить, поскольку он разработан для простоты и удобства использования. Redis, с другой стороны, имеет более богатый набор функций, поэтому он имеет широкий спектр вариантов использования, помимо кэширования. Например, он часто используется для хранения пользовательских сеансов или в качестве брокера сообщений в системе публикации/подписки. Из-за своей гибкости, если вы еще не инвестировали в Memcached, Redis является гораздо лучшим решением.

Чтобы узнать больше об этом, просмотрите этот ответ Stack Overflow.

Затем выберите хранилище данных по вашему выбору, и давайте посмотрим, как кэшировать представление.

Вариант 1: Redis с Django

Загрузите и установите Redis.

Если вы работаете на Mac, мы рекомендуем установить Redis с Homebrew :

$ варить установить Redis

После установки в новом окне терминала запустите сервер Redis и убедитесь, что он работает на своем порту по умолчанию, 6379. Номер порта будет важен, когда мы сообщим Django, как общаться с Redis.

$ redis-server

Чтобы Django использовал Redis в качестве бэкенда кэша, нам сначала нужно установить django-redis .

Добавьте его в файл requirements.txt :

django-redis==5.0.0

Установить:

(venv)$ pip install -r requirements.txt

Затем добавьте пользовательский бэкенд в файл settings.py :

CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': 'redis://127.0.0.1:6379/1',
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient',
        }
    }
}

Теперь, когда вы снова запустите сервер, Redis будет использоваться в качестве бэкенда кеша:

(venv)$ python manage.py runserver

Когда сервер запущен и работает, перейдите по адресу http://127.0.0.1:8000 .

Первый запрос все равно займет около двух секунд. Обновите страницу. Страница должна загрузиться почти мгновенно. Посмотрите на время загрузки в вашем терминале. Он должен быть близок к нулю:

Total time: 0.01s

Любопытно, как выглядят кэшированные данные внутри Redis?

Запустите Redis CLI в интерактивном режиме в новом окне терминала:

$ redis-cli

Тебе следует увидеть:

127.0.0.1:6379>

Запустите ping, чтобы убедиться, что все работает правильно:

127.0.0.1:6379> ping
PONG

Вернитесь к файлу настроек. Мы использовали базу данных Redis №1: 'LOCATION': 'redis://127.0.0.1:6379/1',. Итак, запустите, select 1чтобы выбрать эту базу данных, а затем запустите keys *, чтобы просмотреть все ключи:

127.0.0.1:6379> select 1
OK

127.0.0.1:6379[1]> keys *
1) ":1:views.decorators.cache.cache_header..17abf5259517d604cc9599a00b7385d6.en-us.UTC"
2) ":1:views.decorators.cache.cache_page..GET.17abf5259517d604cc9599a00b7385d6.d41d8cd98f00b204e9800998ecf8427e.en-us.UTC"

Мы видим, что Джанго вставил один ключ заголовка и один cache_pageключ.

Чтобы просмотреть фактические кэшированные данные, запустите getкоманду с ключом в качестве аргумента:

127.0.0.1:6379[1]> get ":1:views.decorators.cache.cache_page..GET.17abf5259517d604cc9599a00b7385d6.d41d8cd98f00b204e9800998ecf8427e.en-us.UTC"

Вы должны увидеть что-то похожее на:

"\x80\x05\x95D\x04\x00\x00\x00\x00\x00\x00\x8c\x18django.template.response\x94\x8c\x10TemplateResponse
\x94\x93\x94)\x81\x94}\x94(\x8c\x05using\x94N\x8c\b_headers\x94}\x94(\x8c\x0ccontent-type\x94\x8c\
x0cContent-Type\x94\x8c\x18text/html; charset=utf-8\x94\x86\x94\x8c\aexpires\x94\x8c\aExpires\x94\x8c\x1d
Fri, 01 May 2020 13:36:59 GMT\x94\x86\x94\x8c\rcache-control\x94\x8c\rCache-Control\x94\x8c\x0
bmax-age=300\x94\x86\x94u\x8c\x11_resource_closers\x94]\x94\x8c\x0e_handler_class\x94N\x8c\acookies
\x94\x8c\x0chttp.cookies\x94\x8c\x0cSimpleCookie\x94\x93\x94)\x81\x94\x8c\x06closed\x94\x89\x8c
\x0e_reason_phrase\x94N\x8c\b_charset\x94N\x8c\n_container\x94]\x94B\xaf\x02\x00\x00
<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Home</title>\n
<link rel=\"stylesheet\" href=\"https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css\
"\n          integrity=\"sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh\"
crossorigin=\"anonymous\">\n\n</head>\n<body>\n<div class=\"container\">\n    <div class=\"pt-3\">\n
<h1>Below is the result of the APICall</h1>\n    </div>\n    <div class=\"pt-3 pb-3\">\n
<a href=\"/\">\n            <button type=\"button\" class=\"btn btn-success\">\n
Get new data\n            </button>\n        </a>\n    </div>\n    Results received!<br>\n
13:31:59\n</div>\n</body>\n</html>\x94a\x8c\x0c_is_rendered\x94\x88ub."

После завершения выйдите из интерактивного интерфейса командной строки:

127.0.0.1:6379[1]> exit

Перейдите к разделу «Тесты производительности».

Вариант 2: Memcached с Django

Начните с добавления pymemcache в файл requirements.txt :

pymemcache==3.5.0

Установите зависимости:

(venv)$ pip install -r requirements.txt

Затем нам нужно обновить настройки в core/settings.py, чтобы включить бэкенд Memcached:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
        'LOCATION': '127.0.0.1:11211',
    }
}

Здесь мы добавили серверную часть PyMemcacheCache и указали, что Memcached должен работать на нашем локальном компьютере на локальном хосте (127.0.0.1) с портом 11211, который является портом по умолчанию для Memcached.

Далее нам нужно установить и запустить демон Memcached. Самый простой способ установить его — через менеджер пакетов, такой как APT, YUM, Homebrew или Chocolatey, в зависимости от вашей операционной системы:

# linux
$ apt-get install memcached
$ yum install memcached

# mac
$ brew install memcached

# windows
$ choco install memcached

Затем запустите его в другом терминале на порту 11211:

$ memcached -p 11211

# test: telnet localhost 11211

Для получения дополнительной информации об установке и настройке Memcached посетите официальную вики .

Снова перейдите по адресу http://127.0.0.1:8000 в нашем браузере. Первый запрос по-прежнему будет занимать полные две секунды, но все последующие запросы будут использовать кэш. Итак, если вы обновите или нажмете кнопку «Получить новые данные», страница должна загрузиться практически мгновенно.

Как выглядит время выполнения в вашем терминале?

Total time: 0.03s

Тесты производительности

Если мы посмотрим на время, необходимое для загрузки первого запроса по сравнению со вторым (кэшированным) запросом на панели инструментов отладки Django, это будет выглядеть примерно так:

время загрузки без кэширования

время загрузки с кешем

Также на панели инструментов отладки вы можете увидеть операции кэширования:

кэш-операции

Снова запустите Gunicorn и повторно запустите тесты производительности:

$ ab -n 100 -c 10 http://127.0.0.1:8000/

Каковы новые запросы в секунду? На моей машине около 36!

Вывод

В этой статье мы рассмотрели различные встроенные параметры кэширования в Django, а также доступные уровни кэширования. Мы также подробно описали, как кэшировать представление с помощью кэша Django для каждого представления как с Memcached, так и с Redis.

Вы можете найти окончательный код для обоих вариантов, Memcached и Redis, в репозитории cache-django-view .

--

Как правило, вам следует обратить внимание на кэширование, когда рендеринг страниц замедляется из-за сетевых задержек из-за запросов к базе данных или HTTP-вызовов.

Оттуда настоятельно рекомендуется использовать собственный кеш-сервер Django с Redis с Per-viewтипом. Если вам нужно больше детализации и контроля, поскольку не все данные в шаблоне одинаковы для всех пользователей или части данных часто изменяются, перейдите к кэшу фрагментов шаблона или API низкоуровневого кэша.

Источник:  https://testdriven.io

#django #caching 

Узнайте о кэшировании в Django

了解 Django 中的緩存

緩存通常是提高應用程序性能的最有效方式。

對於動態網站,在渲染模板時,您通常需要從各種來源(如數據庫、文件系統和第三方 API 等)收集數據,處理數據並將業務邏輯應用於在將其提供給客戶之前。最終用戶將注意到由於網絡延遲導致的任何延遲。

例如,假設您必須對外部 API 進行 HTTP 調用以獲取呈現模板所需的數據。即使在完美的條件下,這也會增加渲染時間,從而增加整體加載時間。如果 API 出現故障或者您可能受到速率限制怎麼辦?無論哪種方式,如果數據不經常更新,最好實現緩存機制以防止必須為每個客戶端請求完全進行 HTTP 調用。

本文著眼於如何做到這一點,首先回顧 Django 的緩存框架作為一個整體,然後逐步詳細說明如何緩存 Django 視圖。

依賴項:

  1. Django v3.2.5
  2. django-redis v5.0.0
  3. Python v3.9.6
  4. pymemcache v3.5.0
  5. 請求 v2.26.0

目標

在本教程結束時,您應該能夠:

  1. 解釋為什麼您可能要考慮緩存 Django 視圖
  2. 描述 Django 可用於緩存的內置選項
  3. 使用 Redis 緩存 Django 視圖
  4. 使用 Apache Bench 對 Django 應用程序進行負載測試
  5. 使用 Memcached 緩存 Django 視圖

Django 緩存類型

Django 帶有幾個內置的緩存後端,以及對自定義後端的支持。

內置選項有:

  1. MemcachedMemcached是一種基於內存的鍵值對小數據塊存儲。它支持跨多個服務器的分佈式緩存。
  2. 數據庫:在這裡,緩存片段存儲在數據庫中。可以使用 Django 的管理命令之一創建用於該目的的表。這不是性能最高的緩存類型,但它對於存儲複雜的數據庫查詢很有用。
  3. 文件系統:緩存保存在文件系統中,每個緩存值都保存在單獨的文件中。這是所有緩存類型中最慢的,但在生產環境中最容易設置。
  4. 本地內存:本地內存緩存,最適合您的本地開發或測試環境。雖然它幾乎與 Memcached 一樣快,但它無法擴展到單個服務器之外,因此它不適合用作任何使用多個 Web 服務器的應用程序的數據緩存。
  5. Dummy:一個“虛擬”緩存,實際上並不緩存任何內容,但仍實現緩存接口。當您不想緩存但又不想更改代碼時,它將用於開發或測試。

Django 緩存級別

Django 中的緩存可以在不同級別(或站點的部分)上實現。您可以緩存整個站點或具有各種粒度級別的特定部分(按粒度降序排列):

每站點緩存

這是在 Django 中實現緩存的最簡單方法。為此,您只需在settings.py文件中添加兩個中間件類:

MIDDLEWARE = [
    'django.middleware.cache.UpdateCacheMiddleware',     # NEW
    'django.middleware.common.CommonMiddleware',
    'django.middleware.cache.FetchFromCacheMiddleware',  # NEW
]

中間件的順序在這裡很重要。UpdateCacheMiddleware必須來之前FetchFromCacheMiddleware。有關更多信息,請查看 Django 文檔中的Order of MIDDLEWARE

然後,您需要添加以下設置:

CACHE_MIDDLEWARE_ALIAS = 'default'  # which cache alias to use
CACHE_MIDDLEWARE_SECONDS = '600'    # number of seconds to cache a page for (TTL)
CACHE_MIDDLEWARE_KEY_PREFIX = ''    # should be used if the cache is shared across multiple sites that use the same Django instance

儘管如果您的站點很少或沒有動態內容,緩存整個站點可能是一個不錯的選擇,但它可能不適合用於具有基於內存的緩存後端的大型站點,因為 RAM 非常昂貴。

按視圖緩存

您可以緩存特定視圖,而不是將寶貴的內存空間浪費在緩存靜態頁面或從快速變化的 API 獲取數據的動態頁面上。這是我們將在本文中使用的方法。它也是您在 Django 應用程序中實現緩存時幾乎總是應該從緩存級別開始的。

您可以直接在視圖函數上或在以下路徑中使用cache_page裝飾器來實現這種類型的緩存URLConf

from django.views.decorators.cache import cache_page

@cache_page(60 * 15)
def your_view(request):
    ...

# or

from django.views.decorators.cache import cache_page

urlpatterns = [
    path('object/<int:object_id>/', cache_page(60 * 15)(your_view)),
]

緩存本身是基於 URL 的,因此請求object/1object/2被單獨緩存。

值得注意的是,直接在視圖上實現緩存使得在某些情況下禁用緩存更加困難。例如,如果您想允許某些用戶在沒有緩存的情況下訪問視圖怎麼辦?通過 啟用緩存可以將URLConf不同的 URL 關聯到不使用緩存的視圖:

from django.views.decorators.cache import cache_page

urlpatterns = [
    path('object/<int:object_id>/', your_view),
    path('object/cache/<int:object_id>/', cache_page(60 * 15)(your_view)),
]

模板片段緩存

如果您的模板包含經常根據數據更改的部分,您可能希望將它們排除在緩存之外。

例如,您可能在模板區域的導航欄中使用經過身份驗證的用戶的電子郵件。好吧,如果您有成千上萬的用戶,那麼該片段將在 RAM 中復制數千次,每個用戶一個。這就是模板片段緩存發揮作用的地方,它允許您指定要緩存的模板的特定區域。

要緩存對象列表:

{% load cache %}

{% cache 500 object_list %}
  <ul>
    {% for object in objects %}
      <li>{{ object.title }}</li>
    {% endfor %}
  </ul>
{% endcache %}

在這裡,{% load cache %}我們可以訪問cache模板標籤,它預計緩存超時(以秒為單位500)以及緩存片段的名稱(object_list)。

低級緩存 API

對於前面的選項沒有提供足夠粒度的情況,您可以使用低級 API 通過緩存鍵來管理緩存中的各個對象。

例如:

from django.core.cache import cache


def get_context_data(self, **kwargs):
    context = super().get_context_data(**kwargs)

    objects = cache.get('objects')

    if objects is None:
        objects = Objects.all()
        cache.set('objects', objects)

    context['objects'] = objects

    return context

在此示例中,您將希望在從數據庫中添加、更改或刪除對象時使緩存無效(或刪除)。一種管理方法是通過數據庫信號:

from django.core.cache import cache
from django.db.models.signals import post_delete, post_save
from django.dispatch import receiver


@receiver(post_delete, sender=Object)
def object_post_delete_handler(sender, **kwargs):
     cache.delete('objects')


@receiver(post_save, sender=Object)
def object_post_save_handler(sender, **kwargs):
    cache.delete('objects')

有關使用數據庫信號使緩存無效的更多信息,請查看Django 中的低級緩存 API文章。

有了這個,讓我們看一些例子。

項目設置

cache-django-view 存儲庫中克隆基礎項目,然後檢查基礎分支:

$ git clone https://github.com/testdrivenio/cache-django-view.git --branch base --single-branch
$ cd cache-django-view

創建(並激活)虛擬環境並安裝要求:

$ python3.9 -m venv venv
$ source venv/bin/activate
(venv)$ pip install -r requirements.txt

應用 Django 遷移,然後啟動服務器:

(venv)$ python manage.py migrate
(venv)$ python manage.py runserver

在您選擇的瀏覽器中導航到http://127.0.0.1:8000以確保一切正常。

你應該看到:

未緩存的網頁

記下您的終端。您應該看到請求的總執行時間:

Total time: 2.23s

該指標來自core/middleware.py

import logging
import time


def metric_middleware(get_response):
    def middleware(request):
        # Get beginning stats
        start_time = time.perf_counter()

        # Process the request
        response = get_response(request)

        # Get ending stats
        end_time = time.perf_counter()

        # Calculate stats
        total_time = end_time - start_time

        # Log the results
        logger = logging.getLogger('debug')
        logger.info(f'Total time: {(total_time):.2f}s')
        print(f'Total time: {(total_time):.2f}s')

        return response

    return middleware

快速查看apicalls/views.py中的視圖:

import datetime

import requests
from django.views.generic import TemplateView


BASE_URL = 'https://httpbin.org/'


class ApiCalls(TemplateView):
    template_name = 'apicalls/home.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        response = requests.get(f'{BASE_URL}/delay/2')
        response.raise_for_status()
        context['content'] = 'Results received!'
        context['current_time'] = datetime.datetime.now()
        return context

requests此視圖對httpbin.org進行 HTTP 調用。為了模擬一個長請求,來自 API 的響應會延遲兩秒鐘。因此,http: //127.0.0.1 :8000不僅在初始請求上呈現,而且對每個後續請求也需要大約兩秒鐘。雖然初始請求的兩秒加載在某種程度上是可以接受的,但對於後續請求來說,這是完全不可接受的,因為數據沒有改變。讓我們通過使用 Django 的 Per-view 緩存級別緩存整個視圖來解決這個問題。

工作流程:

  1. 在初始請求中對httpbin.org進行完整的 HTTP 調用
  2. 緩存視圖
  3. 隨後的請求將從緩存中提取,繞過 HTTP 調用
  4. 一段時間後使緩存失效(TTL)

基準性能基準

在添加緩存之前,讓我們使用Apache Bench快速運行負載測試以獲得基準基線,粗略了解我們的應用程序每秒可以處理多少請求。

Apache Bench 預裝在 Mac 上。

如果您使用的是 Linux 系統,那麼它很可能已經安裝並準備就緒。如果沒有,您可以通過 APT ( apt-get install apache2-utils) 或 YUM ( yum install httpd-tools) 安裝。

Windows 用戶需要下載並解壓 Apache 二進製文件。

Gunicorn添加到需求文件中:

gunicorn==20.1.0

殺死 Django 開發服務器並安裝 Gunicorn:

(venv)$ pip install -r requirements.txt

接下來,使用 Gunicorn(和四個worker)為 Django 應用程序提供服務,如下所示:

(venv)$ gunicorn core.wsgi:application -w 4

在新的終端窗口中,運行 Apache Bench:

$ ab -n 100 -c 10 http://127.0.0.1:8000/

這將模擬 10 個並發線程上的 100 個連接。那是 100 個請求,一次 10 個。

記下每秒的請求數:

Requests per second:    1.69 [#/sec] (mean)

請記住,Django 調試工具欄會增加一些開銷。一般來說,基準測試很難完全正確。重要的是一致性。選擇一個指標來關注並為每個測試使用相同的環境。

殺死 Gunicorn 服務器並重新啟動 Django 開發服務器:

(venv)$ python manage.py runserver

有了這個,讓我們看看如何緩存視圖。

緩存視圖

首先用裝飾器裝飾ApiCalls視圖,@cache_page如下所示:

import datetime

import requests
from django.utils.decorators import method_decorator # NEW
from django.views.decorators.cache import cache_page # NEW
from django.views.generic import TemplateView


BASE_URL = 'https://httpbin.org/'


@method_decorator(cache_page(60 * 5), name='dispatch') # NEW
class ApiCalls(TemplateView):
    template_name = 'apicalls/home.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        response = requests.get(f'{BASE_URL}/delay/2')
        response.raise_for_status()
        context['content'] = 'Results received!'
        context['current_time'] = datetime.datetime.now()
        return context

由於我們使用的是基於類的視圖,我們不能將裝飾器直接放在類上,因此我們使用 amethod_decorator和指定dispatch(作為要裝飾的方法)作為 name 參數。

此示例中的緩存設置了五分鐘的超時(或 TTL)。

或者,您可以在設置中進行設置,如下所示:

# Cache time to live is 5 minutes
CACHE_TTL = 60 * 5

然後,回到視圖中:

import datetime

import requests
from django.conf import settings
from django.core.cache.backends.base import DEFAULT_TIMEOUT
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page
from django.views.generic import TemplateView

BASE_URL = 'https://httpbin.org/'
CACHE_TTL = getattr(settings, 'CACHE_TTL', DEFAULT_TIMEOUT)


@method_decorator(cache_page(CACHE_TTL), name='dispatch')
class ApiCalls(TemplateView):
    template_name = 'apicalls/home.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        response = requests.get(f'{BASE_URL}/delay/2')
        response.raise_for_status()
        context['content'] = 'Results received!'
        context['current_time'] = datetime.datetime.now()
        return context

接下來,讓我們添加一個緩存後端。

Redis 與 Memcached

MemcachedRedis是內存中的鍵值數據存儲。它們易於使用並針對高性能查找進行了優化。您可能不會看到兩者在性能或內存使用方面有太大差異。也就是說,Memcached 的配置稍微容易一些,因為它是為簡單易用而設計的。另一方面,Redis 具有更豐富的功能集,因此除了緩存之外,它還具有廣泛的用例。例如,它通常用於存儲用戶會話或作為發布/訂閱系統中的消息代理。由於它的靈活性,除非您已經投資於 Memcached,否則 Redis 是更好的解決方案。

有關這方面的更多信息,請查看Stack Overflow 答案。

接下來,選擇您選擇的數據存儲,讓我們看看如何緩存視圖。

選項 1:使用 Django 的 Redis

下載並安裝 Redis。

如果您使用的是 Mac,我們建議您使用Homebrew安裝 Redis :

$ brew install redis

安裝後,在新的終端窗口中啟動 Redis 服務器並確保它在其默認端口 6379 上運行。當我們告訴 Django 如何與 Redis 通信時,端口號將很重要。

$ redis-server

為了讓 Django 使用 Redis 作為緩存後端,我們首先需要安裝django-redis

將其添加到requirements.txt文件中:

django-redis==5.0.0

安裝:

(venv)$ pip install -r requirements.txt

接下來,將自定義後端添加到settings.py文件中:

CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': 'redis://127.0.0.1:6379/1',
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient',
        }
    }
}

現在,當您再次運行服務器時,Redis 將用作緩存後端:

(venv)$ python manage.py runserver

服務器啟動並運行後,導航到http://127.0.0.1:8000

第一個請求仍然需要大約兩秒鐘。刷新頁面。該頁面應該幾乎立即加載。查看終端中的加載時間。它應該接近於零:

Total time: 0.01s

好奇緩存數據在 Redis 中的樣子?

在新的終端窗口中以交互模式運行 Redis CLI:

$ redis-cli

你應該看到:

127.0.0.1:6379>

運行ping以確保一切正常:

127.0.0.1:6379> ping
PONG

返回設置文件。我們使用了 Redis 數據庫 1 'LOCATION': 'redis://127.0.0.1:6379/1',:. 因此,運行select 1以選擇該數據庫,然後運行keys *以查看所有鍵:

127.0.0.1:6379> select 1
OK

127.0.0.1:6379[1]> keys *
1) ":1:views.decorators.cache.cache_header..17abf5259517d604cc9599a00b7385d6.en-us.UTC"
2) ":1:views.decorators.cache.cache_page..GET.17abf5259517d604cc9599a00b7385d6.d41d8cd98f00b204e9800998ecf8427e.en-us.UTC"

我們可以看到 Django 放入了一個 header key 和一個cache_pagekey。

要查看實際緩存的數據,請get使用鍵作為參數運行命令:

127.0.0.1:6379[1]> get ":1:views.decorators.cache.cache_page..GET.17abf5259517d604cc9599a00b7385d6.d41d8cd98f00b204e9800998ecf8427e.en-us.UTC"

您應該看到類似於以下內容的內容:

"\x80\x05\x95D\x04\x00\x00\x00\x00\x00\x00\x8c\x18django.template.response\x94\x8c\x10TemplateResponse
\x94\x93\x94)\x81\x94}\x94(\x8c\x05using\x94N\x8c\b_headers\x94}\x94(\x8c\x0ccontent-type\x94\x8c\
x0cContent-Type\x94\x8c\x18text/html; charset=utf-8\x94\x86\x94\x8c\aexpires\x94\x8c\aExpires\x94\x8c\x1d
Fri, 01 May 2020 13:36:59 GMT\x94\x86\x94\x8c\rcache-control\x94\x8c\rCache-Control\x94\x8c\x0
bmax-age=300\x94\x86\x94u\x8c\x11_resource_closers\x94]\x94\x8c\x0e_handler_class\x94N\x8c\acookies
\x94\x8c\x0chttp.cookies\x94\x8c\x0cSimpleCookie\x94\x93\x94)\x81\x94\x8c\x06closed\x94\x89\x8c
\x0e_reason_phrase\x94N\x8c\b_charset\x94N\x8c\n_container\x94]\x94B\xaf\x02\x00\x00
<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Home</title>\n
<link rel=\"stylesheet\" href=\"https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css\
"\n          integrity=\"sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh\"
crossorigin=\"anonymous\">\n\n</head>\n<body>\n<div class=\"container\">\n    <div class=\"pt-3\">\n
<h1>Below is the result of the APICall</h1>\n    </div>\n    <div class=\"pt-3 pb-3\">\n
<a href=\"/\">\n            <button type=\"button\" class=\"btn btn-success\">\n
Get new data\n            </button>\n        </a>\n    </div>\n    Results received!<br>\n
13:31:59\n</div>\n</body>\n</html>\x94a\x8c\x0c_is_rendered\x94\x88ub."

完成後退出交互式 CLI:

127.0.0.1:6379[1]> exit

跳到“性能測試”部分。

選項 2:使用 Django 進行 Memcached

首先將pymemcache添加到requirements.txt文件中:

pymemcache==3.5.0

安裝依賴項:

(venv)$ pip install -r requirements.txt

接下來,我們需要更新core/settings.py中的設置以啟用 Memcached 後端:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
        'LOCATION': '127.0.0.1:11211',
    }
}

在這裡,我們添加了PyMemcacheCache後端,並指示 Memcached 應該在我們的本地計算機上運行 localhost (127.0.0.1) 端口 11211,這是 Memcached 的默認端口。

接下來,我們需要安裝並運行 Memcached 守護進程。安裝它的最簡單方法是通過 APT、YUM、Homebrew 或 Chocolatey 等軟件包管理器,具體取決於您的操作系統:

# linux
$ apt-get install memcached
$ yum install memcached

# mac
$ brew install memcached

# windows
$ choco install memcached

然後,在端口 11211 上的不同終端中運行它:

$ memcached -p 11211

# test: telnet localhost 11211

有關 Memcached 安裝和配置的更多信息,請查看官方wiki

再次在我們的瀏覽器中導航到http://127.0.0.1:8000。第一個請求仍然需要整整兩秒鐘,但所有後續請求都將利用緩存。因此,如果您刷新或按下“獲取新數據按鈕”,頁面應該幾乎立即加載。

您的終端中的執行時間是什麼樣的?

Total time: 0.03s

性能測試

如果我們查看在 Django 調試工具欄中加載第一個請求與第二個(緩存)請求所花費的時間,它看起來類似於:

加載時間未緩存

帶緩存的加載時間

同樣在調試工具欄中,您可以看到緩存操作:

緩存操作

再次旋轉 Gunicorn 並重新運行性能測試:

$ ab -n 100 -c 10 http://127.0.0.1:8000/

每秒有多少新請求?在我的機器上大約是 36!

結論

在本文中,我們研究了 Django 中用於緩存的不同內置選項以及可用的不同級別的緩存。我們還詳細介紹瞭如何使用 Django 的 Per-view 緩存以及 Memcached 和 Redis 來緩存視圖。

您可以在cache-django-view 存儲庫中找到 Memcached 和 Redis 這兩個選項的最終代碼。

--

通常,當由於數據庫查詢或 HTTP 調用的網絡延遲導致頁面呈現速度較慢時,您需要考慮緩存。

從那裡開始,強烈建議使用帶有Per-view類型的 Redis 的自定義 Django 緩存後端。如果你需要更多的粒度和控制,因為不是所有用戶的模板上的所有數據都是相同的,或者部分數據經常變化,然後跳到模板片段緩存或低級緩存API。

來源:  https ://testdriven.io

#django #caching 

了解 Django 中的緩存
Noelia  Graham

Noelia Graham

1660884840

En Savoir Plus Sur La Mise En Cache Dans Django

La mise en cache est généralement le moyen le plus efficace d'améliorer les performances d'une application.

Pour les sites Web dynamiques, lors du rendu d'un modèle, vous devrez souvent collecter des données à partir de diverses sources (comme une base de données, le système de fichiers et des API tierces, pour n'en nommer que quelques-unes), traiter les données et appliquer une logique métier à avant de le servir à un client. Tout retard dû à la latence du réseau sera remarqué par l'utilisateur final.

Par exemple, supposons que vous deviez effectuer un appel HTTP à une API externe pour récupérer les données nécessaires au rendu d'un modèle. Même dans des conditions parfaites, cela augmentera le temps de rendu, ce qui augmentera le temps de chargement global. Que se passe-t-il si l'API tombe en panne ou si vous êtes soumis à une limitation de débit ? Quoi qu'il en soit, si les données sont rarement mises à jour, il est judicieux d'implémenter un mécanisme de mise en cache pour éviter d'avoir à effectuer l'appel HTTP pour chaque demande client.

Cet article explique comment faire exactement cela en examinant d'abord le cadre de mise en cache de Django dans son ensemble, puis en détaillant étape par étape comment mettre en cache une vue Django.

Dépendances :

  1. Django v3.2.5
  2. django-redis v5.0.0
  3. Python v3.9.6
  4. pymemcache v3.5.0
  5. Demandes v2.26.0

Objectifs

À la fin de ce didacticiel, vous devriez être en mesure de :

  1. Expliquez pourquoi vous pourriez envisager de mettre en cache une vue Django
  2. Décrire les options intégrées de Django disponibles pour la mise en cache
  3. Mettre en cache une vue Django avec Redis
  4. Tester en charge une application Django avec Apache Bench
  5. Mettre en cache une vue Django avec Memcached

Types de cache Django

Django est livré avec plusieurs backends de mise en cache intégrés, ainsi que la prise en charge d'un backend personnalisé .

Les options intégrées sont :

  1. Memcached : Memcached est un magasin clé-valeur basé sur la mémoire pour de petits morceaux de données. Il prend en charge la mise en cache distribuée sur plusieurs serveurs.
  2. Base de données : Ici, les fragments de cache sont stockés dans une base de données. Une table à cet effet peut être créée avec l'une des commandes d'administration de Django. Ce n'est pas le type de mise en cache le plus performant, mais il peut être utile pour stocker des requêtes de base de données complexes.
  3. Système de fichiers : Le cache est enregistré sur le système de fichiers, dans des fichiers séparés pour chaque valeur de cache. C'est le plus lent de tous les types de mise en cache, mais c'est le plus facile à configurer dans un environnement de production.
  4. Mémoire locale : cache de la mémoire locale, qui convient le mieux à vos environnements de développement ou de test locaux. Bien qu'il soit presque aussi rapide que Memcached, il ne peut pas évoluer au-delà d'un seul serveur, il n'est donc pas approprié de l'utiliser comme cache de données pour toute application qui utilise plus d'un serveur Web.
  5. Dummy : Un cache "factice" qui ne cache rien mais qui implémente toujours l'interface de cache. Il est destiné à être utilisé dans le développement ou les tests lorsque vous ne souhaitez pas de mise en cache, mais que vous ne souhaitez pas modifier votre code.

Niveaux de mise en cache de Django

La mise en cache dans Django peut être implémentée à différents niveaux (ou parties du site). Vous pouvez mettre en cache l'intégralité du site ou des parties spécifiques avec différents niveaux de granularité (répertoriés par ordre décroissant de granularité) :

Per-site cache

C'est le moyen le plus simple d'implémenter la mise en cache dans Django. Pour cela, il vous suffira d'ajouter deux classes middleware à votre fichier settings.py :

MIDDLEWARE = [
    'django.middleware.cache.UpdateCacheMiddleware',     # NEW
    'django.middleware.common.CommonMiddleware',
    'django.middleware.cache.FetchFromCacheMiddleware',  # NEW
]

L'ordre du middleware est important ici. UpdateCacheMiddlewaredoit venir avant FetchFromCacheMiddleware. Pour plus d'informations, consultez Order of MIDDLEWARE dans la documentation Django.

Vous devez ensuite ajouter les paramètres suivants :

CACHE_MIDDLEWARE_ALIAS = 'default'  # which cache alias to use
CACHE_MIDDLEWARE_SECONDS = '600'    # number of seconds to cache a page for (TTL)
CACHE_MIDDLEWARE_KEY_PREFIX = ''    # should be used if the cache is shared across multiple sites that use the same Django instance

Bien que la mise en cache de l'intégralité du site puisse être une bonne option si votre site a peu ou pas de contenu dynamique, il peut ne pas être approprié de l'utiliser pour les grands sites avec un backend de cache basé sur la mémoire car la RAM est, eh bien, chère.

Cache par vue

Plutôt que de gaspiller de l'espace mémoire précieux en mettant en cache des pages statiques ou des pages dynamiques dont les données proviennent d'une API en évolution rapide, vous pouvez mettre en cache des vues spécifiques. C'est l'approche que nous allons utiliser dans cet article. C'est aussi le niveau de mise en cache avec lequel vous devriez presque toujours commencer lorsque vous cherchez à implémenter la mise en cache dans votre application Django.

Vous pouvez implémenter ce type de cache avec le décorateur cache_page soit sur la fonction d'affichage directement, soit dans le chemin dans URLConf:

from django.views.decorators.cache import cache_page

@cache_page(60 * 15)
def your_view(request):
    ...

# or

from django.views.decorators.cache import cache_page

urlpatterns = [
    path('object/<int:object_id>/', cache_page(60 * 15)(your_view)),
]

The cache itself is based on the URL, so requests to, say, object/1 and object/2 will be cached separately.

It's worth noting that implementing the cache directly on the view makes it more difficult to disable the cache in certain situations. For example, what if you wanted to allow certain users access to the view without the cache? Enabling the cache via the URLConf provides the opportunity to associate a different URL to the view that doesn't use the cache:

from django.views.decorators.cache import cache_page

urlpatterns = [
    path('object/<int:object_id>/', your_view),
    path('object/cache/<int:object_id>/', cache_page(60 * 15)(your_view)),
]

Template fragment cache

If your templates contain parts that change often based on the data you'll probably want to leave them out of the cache.

Par exemple, vous utilisez peut-être l'e-mail de l'utilisateur authentifié dans la barre de navigation d'une zone du modèle. Eh bien, si vous avez des milliers d'utilisateurs, ce fragment sera dupliqué des milliers de fois dans la RAM, un pour chaque utilisateur. C'est là que la mise en cache des fragments de modèle entre en jeu, ce qui vous permet de spécifier les zones spécifiques d'un modèle à mettre en cache.

Pour mettre en cache une liste d'objets :

{% load cache %}

{% cache 500 object_list %}
  <ul>
    {% for object in objects %}
      <li>{{ object.title }}</li>
    {% endfor %}
  </ul>
{% endcache %}

Ici, {% load cache %}nous donne accès à la cachebalise de modèle, qui attend un délai d'expiration du cache en secondes ( 500) avec le nom du fragment de cache ( object_list).

API de cache de bas niveau

Dans les cas où les options précédentes ne fournissent pas suffisamment de granularité, vous pouvez utiliser l'API de bas niveau pour gérer des objets individuels dans le cache par clé de cache.

Par exemple:

from django.core.cache import cache


def get_context_data(self, **kwargs):
    context = super().get_context_data(**kwargs)

    objects = cache.get('objects')

    if objects is None:
        objects = Objects.all()
        cache.set('objects', objects)

    context['objects'] = objects

    return context

Dans cet exemple, vous souhaiterez invalider (ou supprimer) le cache lorsque des objets sont ajoutés, modifiés ou supprimés de la base de données. Une façon de gérer cela consiste à utiliser des signaux de base de données :

from django.core.cache import cache
from django.db.models.signals import post_delete, post_save
from django.dispatch import receiver


@receiver(post_delete, sender=Object)
def object_post_delete_handler(sender, **kwargs):
     cache.delete('objects')


@receiver(post_save, sender=Object)
def object_post_save_handler(sender, **kwargs):
    cache.delete('objects')

Pour en savoir plus sur l'utilisation des signaux de base de données pour invalider le cache, consultez l' article de l' API de cache de bas niveau dans Django .

Sur ce, regardons quelques exemples.

Configuration du projet

Clonez le projet de base à partir du référentiel cache-django-view , puis consultez la branche de base :

$ git clone https://github.com/testdrivenio/cache-django-view.git --branch base --single-branch
$ cd cache-django-view

Créez (et activez) un environnement virtuel et installez la configuration requise :

$ python3.9 -m venv venv
$ source venv/bin/activate
(venv)$ pip install -r requirements.txt

Appliquez les migrations Django, puis démarrez le serveur :

(venv)$ python manage.py migrate
(venv)$ python manage.py runserver

Accédez à http://127.0.0.1:8000 dans le navigateur de votre choix pour vous assurer que tout fonctionne comme prévu.

Tu devrais voir:

page Web non mise en cache

Prenez note de votre terminal. Vous devriez voir le temps d'exécution total de la requête :

Total time: 2.23s

Cette métrique provient de core/middleware.py :

import logging
import time


def metric_middleware(get_response):
    def middleware(request):
        # Get beginning stats
        start_time = time.perf_counter()

        # Process the request
        response = get_response(request)

        # Get ending stats
        end_time = time.perf_counter()

        # Calculate stats
        total_time = end_time - start_time

        # Log the results
        logger = logging.getLogger('debug')
        logger.info(f'Total time: {(total_time):.2f}s')
        print(f'Total time: {(total_time):.2f}s')

        return response

    return middleware

Jetez un coup d'œil à la vue dans apicalls/views.py :

import datetime

import requests
from django.views.generic import TemplateView


BASE_URL = 'https://httpbin.org/'


class ApiCalls(TemplateView):
    template_name = 'apicalls/home.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        response = requests.get(f'{BASE_URL}/delay/2')
        response.raise_for_status()
        context['content'] = 'Results received!'
        context['current_time'] = datetime.datetime.now()
        return context

Cette vue effectue un appel HTTP requestsavec httpbin.org . Pour simuler une requête longue, la réponse de l'API est retardée de deux secondes. Ainsi, cela devrait prendre environ deux secondes pour que http://127.0.0.1:8000 s'affiche non seulement sur la requête initiale, mais également pour chaque requête suivante. Alors qu'un chargement de deux secondes est quelque peu acceptable sur la requête initiale, il est totalement inacceptable pour les requêtes suivantes puisque les données ne changent pas. Corrigeons cela en mettant en cache la vue entière à l'aide du niveau de cache par vue de Django.

Flux de travail :

  1. Faire un appel HTTP complet à httpbin.org sur la demande initiale
  2. Mettre la vue en cache
  3. Les requêtes suivantes seront ensuite extraites du cache, en contournant l'appel HTTP
  4. Invalider le cache après un certain temps (TTL)

Référence de performance de base

Avant d'ajouter du cache, exécutons rapidement un test de charge pour obtenir une référence de référence à l'aide d' Apache Bench , afin d'avoir une idée approximative du nombre de requêtes que notre application peut gérer par seconde.

Apache Bench est préinstallé sur Mac.

Si vous utilisez un système Linux, il y a de fortes chances qu'il soit déjà installé et prêt à fonctionner également. Sinon, vous pouvez installer via APT ( apt-get install apache2-utils) ou YUM ( yum install httpd-tools).

Les utilisateurs de Windows devront télécharger et extraire les binaires Apache.

Ajoutez Gunicorn au fichier requirements :

gunicorn==20.1.0

Tuez le serveur de développement Django et installez Gunicorn :

(venv)$ pip install -r requirements.txt

Ensuite, servez l'application Django avec Gunicorn (et quatre travailleurs ) comme ceci :

(venv)$ gunicorn core.wsgi:application -w 4

Dans une nouvelle fenêtre de terminal, exécutez Apache Bench :

$ ab -n 100 -c 10 http://127.0.0.1:8000/

Cela simulera 100 connexions sur 10 threads simultanés. C'est 100 demandes, 10 à la fois.

Prenez note des requêtes par seconde :

Requests per second:    1.69 [#/sec] (mean)

Gardez à l'esprit que Django Debug Toolbar ajoutera un peu de surcharge. L'analyse comparative en général est difficile à obtenir parfaitement. L'important, c'est la cohérence. Choisissez une métrique sur laquelle vous concentrer et utilisez le même environnement pour chaque test.

Tuez le serveur Gunicorn et relancez le serveur de développement Django :

(venv)$ python manage.py runserver

Sur ce, regardons comment mettre en cache une vue.

Mise en cache d'une vue

Commencez par décorer la ApiCallsvue avec le @cache_pagedécorateur comme ceci :

import datetime

import requests
from django.utils.decorators import method_decorator # NEW
from django.views.decorators.cache import cache_page # NEW
from django.views.generic import TemplateView


BASE_URL = 'https://httpbin.org/'


@method_decorator(cache_page(60 * 5), name='dispatch') # NEW
class ApiCalls(TemplateView):
    template_name = 'apicalls/home.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        response = requests.get(f'{BASE_URL}/delay/2')
        response.raise_for_status()
        context['content'] = 'Results received!'
        context['current_time'] = datetime.datetime.now()
        return context

Comme nous utilisons une vue basée sur les classes, nous ne pouvons pas placer le décorateur directement sur la classe, nous avons donc utilisé a method_decoratoret spécifié dispatch(comme méthode à décorer) pour l'argument name.

Le cache dans cet exemple définit un délai d'attente (ou TTL) de cinq minutes.

Alternativement, vous pouvez définir ceci dans vos paramètres comme suit :

# Cache time to live is 5 minutes
CACHE_TTL = 60 * 5

Puis, de retour dans la vue :

import datetime

import requests
from django.conf import settings
from django.core.cache.backends.base import DEFAULT_TIMEOUT
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page
from django.views.generic import TemplateView

BASE_URL = 'https://httpbin.org/'
CACHE_TTL = getattr(settings, 'CACHE_TTL', DEFAULT_TIMEOUT)


@method_decorator(cache_page(CACHE_TTL), name='dispatch')
class ApiCalls(TemplateView):
    template_name = 'apicalls/home.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        response = requests.get(f'{BASE_URL}/delay/2')
        response.raise_for_status()
        context['content'] = 'Results received!'
        context['current_time'] = datetime.datetime.now()
        return context

Ensuite, ajoutons un backend de cache.

Redis contre Memcached

Memcached et Redis sont des magasins de données clé-valeur en mémoire. Ils sont faciles à utiliser et optimisés pour des recherches hautes performances. Vous ne verrez probablement pas beaucoup de différence de performances ou d'utilisation de la mémoire entre les deux. Cela dit, Memcached est légèrement plus facile à configurer car il est conçu pour la simplicité et la facilité d'utilisation. Redis, en revanche, possède un ensemble de fonctionnalités plus riche, ce qui lui permet de proposer un large éventail de cas d'utilisation au-delà de la mise en cache. Par exemple, il est souvent utilisé pour stocker des sessions utilisateur ou comme courtier de messages dans un système pub/sub. En raison de sa flexibilité, à moins que vous ne soyez déjà investi dans Memcached, Redis est une bien meilleure solution.

Pour en savoir plus, consultez cette réponse Stack Overflow.

Ensuite, choisissez le magasin de données de votre choix et voyons comment mettre en cache une vue.

Option 1 : Redis avec Django

Téléchargez et installez Redis.

Si vous êtes sur Mac, nous vous recommandons d'installer Redis avec Homebrew :

$ brasser installer redis

Une fois installé, dans une nouvelle fenêtre de terminal, démarrez le serveur Redis et assurez-vous qu'il fonctionne sur son port par défaut, 6379. Le numéro de port sera important lorsque nous indiquerons à Django comment communiquer avec Redis.

$ redis-server

Pour que Django utilise Redis comme moteur de cache, nous devons d'abord installer django-redis .

Ajoutez-le au fichier requirements.txt :

django-redis==5.0.0

Installer:

(venv)$ pip install -r requirements.txt

Ensuite, ajoutez le backend personnalisé au fichier settings.py :

CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': 'redis://127.0.0.1:6379/1',
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient',
        }
    }
}

Désormais, lorsque vous relancerez le serveur, Redis sera utilisé comme backend de cache :

(venv)$ python manage.py runserver

Avec le serveur opérationnel, accédez à http://127.0.0.1:8000 .

La première demande prendra encore environ deux secondes. Actualiser la page. La page devrait se charger presque instantanément. Regardez le temps de chargement dans votre terminal. Il doit être proche de zéro :

Total time: 0.01s

Vous voulez savoir à quoi ressemblent les données mises en cache dans Redis ?

Exécutez Redis CLI en mode interactif dans une nouvelle fenêtre de terminal :

$ redis-cli

Tu devrais voir:

127.0.0.1:6379>

Exécutez pingpour vous assurer que tout fonctionne correctement :

127.0.0.1:6379> ping
PONG

Revenez au fichier de paramètres. Nous avons utilisé la base de données Redis numéro 1 : 'LOCATION': 'redis://127.0.0.1:6379/1',. Alors, exécutez select 1pour sélectionner cette base de données, puis exécutez keys *pour afficher toutes les clés :

127.0.0.1:6379> select 1
OK

127.0.0.1:6379[1]> keys *
1) ":1:views.decorators.cache.cache_header..17abf5259517d604cc9599a00b7385d6.en-us.UTC"
2) ":1:views.decorators.cache.cache_page..GET.17abf5259517d604cc9599a00b7385d6.d41d8cd98f00b204e9800998ecf8427e.en-us.UTC"

Nous pouvons voir que Django a mis une clé d'en-tête et une cache_pageclé.

Pour afficher les données réelles mises en cache, exécutez la getcommande avec la clé comme argument :

127.0.0.1:6379[1]> get ":1:views.decorators.cache.cache_page..GET.17abf5259517d604cc9599a00b7385d6.d41d8cd98f00b204e9800998ecf8427e.en-us.UTC"

Vous devriez voir quelque chose de similaire à :

"\x80\x05\x95D\x04\x00\x00\x00\x00\x00\x00\x8c\x18django.template.response\x94\x8c\x10TemplateResponse
\x94\x93\x94)\x81\x94}\x94(\x8c\x05using\x94N\x8c\b_headers\x94}\x94(\x8c\x0ccontent-type\x94\x8c\
x0cContent-Type\x94\x8c\x18text/html; charset=utf-8\x94\x86\x94\x8c\aexpires\x94\x8c\aExpires\x94\x8c\x1d
Fri, 01 May 2020 13:36:59 GMT\x94\x86\x94\x8c\rcache-control\x94\x8c\rCache-Control\x94\x8c\x0
bmax-age=300\x94\x86\x94u\x8c\x11_resource_closers\x94]\x94\x8c\x0e_handler_class\x94N\x8c\acookies
\x94\x8c\x0chttp.cookies\x94\x8c\x0cSimpleCookie\x94\x93\x94)\x81\x94\x8c\x06closed\x94\x89\x8c
\x0e_reason_phrase\x94N\x8c\b_charset\x94N\x8c\n_container\x94]\x94B\xaf\x02\x00\x00
<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Home</title>\n
<link rel=\"stylesheet\" href=\"https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css\
"\n          integrity=\"sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh\"
crossorigin=\"anonymous\">\n\n</head>\n<body>\n<div class=\"container\">\n    <div class=\"pt-3\">\n
<h1>Below is the result of the APICall</h1>\n    </div>\n    <div class=\"pt-3 pb-3\">\n
<a href=\"/\">\n            <button type=\"button\" class=\"btn btn-success\">\n
Get new data\n            </button>\n        </a>\n    </div>\n    Results received!<br>\n
13:31:59\n</div>\n</body>\n</html>\x94a\x8c\x0c_is_rendered\x94\x88ub."

Quittez la CLI interactive une fois terminé :

127.0.0.1:6379[1]> exit

Passez à la section "Tests de performance".

Option 2 : Memcaché avec Django

Commencez par ajouter pymemcache au fichier requirements.txt :

pymemcache==3.5.0

Installez les dépendances :

(venv)$ pip install -r requirements.txt

Ensuite, nous devons mettre à jour les paramètres dans core/settings.py pour activer le backend Memcached :

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
        'LOCATION': '127.0.0.1:11211',
    }
}

Ici, nous avons ajouté le backend PyMemcacheCache et indiqué que Memcached devrait être exécuté sur notre machine locale sur le port localhost (127.0.0.1) 11211, qui est le port par défaut pour Memcached.

Ensuite, nous devons installer et exécuter le démon Memcached. Le moyen le plus simple de l'installer est via un gestionnaire de packages comme APT, YUM, Homebrew ou Chocolatey selon votre système d'exploitation :

# linux
$ apt-get install memcached
$ yum install memcached

# mac
$ brew install memcached

# windows
$ choco install memcached

Ensuite, exécutez-le dans un autre terminal sur le port 11211 :

$ memcached -p 11211

# test: telnet localhost 11211

Pour plus d'informations sur l'installation et la configuration de Memcached, consultez le wiki officiel .

Accédez à nouveau à http://127.0.0.1:8000 dans notre navigateur. La première requête prendra toujours les deux secondes complètes, mais toutes les requêtes suivantes tireront parti du cache. Ainsi, si vous actualisez ou appuyez sur le bouton "Obtenir de nouvelles données", la page devrait se charger presque instantanément.

À quoi ressemble le temps d'exécution dans votre terminal ?

Total time: 0.03s

Des tests de performance

Si nous regardons le temps qu'il faut pour charger la première requête par rapport à la deuxième requête (en cache) dans Django Debug Toolbar, cela ressemblera à :

temps de chargement non mis en cache

temps de chargement avec cache

Également dans la barre d'outils de débogage, vous pouvez voir les opérations de cache :

opérations de cache

Faites à nouveau tourner Gunicorn et relancez les tests de performances :

$ ab -n 100 -c 10 http://127.0.0.1:8000/

Quelles sont les nouvelles requêtes par seconde ? C'est environ 36 sur ma machine !

Conclusion

Dans cet article, nous avons examiné les différentes options intégrées de mise en cache dans Django ainsi que les différents niveaux de mise en cache disponibles. Nous avons également détaillé comment mettre en cache une vue à l'aide du cache Per-view de Django avec Memcached et Redis.

Vous pouvez trouver le code final pour les deux options, Memcached et Redis, dans le référentiel cache-django-view .

--

En général, vous souhaiterez rechercher la mise en cache lorsque le rendu de la page est lent en raison de la latence du réseau à partir des requêtes de base de données ou des appels HTTP.

À partir de là, il est fortement recommandé d'utiliser un backend de cache Django personnalisé avec Redis avec un Per-viewtype. Si vous avez besoin de plus de granularité et de contrôle, car toutes les données du modèle ne sont pas identiques pour tous les utilisateurs ou certaines parties des données changent fréquemment, passez au cache de fragment de modèle ou à l'API de cache de bas niveau.

Source :  https://testdrive.io

#django #caching 

En Savoir Plus Sur La Mise En Cache Dans Django

Aprenda Sobre El Almacenamiento En Caché En Django

El almacenamiento en caché suele ser la forma más eficaz de aumentar el rendimiento de una aplicación.

Para sitios web dinámicos, al renderizar una plantilla, a menudo tendrá que recopilar datos de varias fuentes (como una base de datos, el sistema de archivos y API de terceros, por nombrar algunos), procesar los datos y aplicar la lógica comercial a antes de servirlo a un cliente. El usuario final notará cualquier retraso debido a la latencia de la red.

Por ejemplo, supongamos que debe realizar una llamada HTTP a una API externa para obtener los datos necesarios para representar una plantilla. Incluso en perfectas condiciones, esto aumentará el tiempo de renderizado, lo que aumentará el tiempo de carga general. ¿Qué sucede si la API deja de funcionar o tal vez está sujeto a una limitación de velocidad? De cualquier manera, si los datos se actualizan con poca frecuencia, es una buena idea implementar un mecanismo de almacenamiento en caché para evitar tener que realizar la llamada HTTP por completo para cada solicitud del cliente.

Este artículo analiza cómo hacer precisamente eso revisando primero el marco de almacenamiento en caché de Django en su conjunto y luego detallando paso a paso cómo almacenar en caché una vista de Django.

Dependencias:

  1. Django v3.2.5
  2. Django-redis v5.0.0
  3. Pitón v3.9.6
  4. pymemcache v3.5.0
  5. Solicitudes v2.26.0

Objetivos

Al final de este tutorial, debería ser capaz de:

  1. Explique por qué es posible que desee considerar almacenar en caché una vista de Django
  2. Describir las opciones incorporadas de Django disponibles para el almacenamiento en caché
  3. Almacenar en caché una vista de Django con Redis
  4. Prueba de carga una aplicación Django con Apache Bench
  5. Almacenar en caché una vista de Django con Memcached

Tipos de almacenamiento en caché de Django

Django viene con varios backends de almacenamiento en caché incorporados , así como soporte para un backend personalizado .

Las opciones integradas son:

  1. Memcached : Memcached es un almacén de clave-valor basado en memoria para pequeños fragmentos de datos. Admite el almacenamiento en caché distribuido en varios servidores.
  2. Base de datos : aquí, los fragmentos de caché se almacenan en una base de datos. Se puede crear una tabla para ese propósito con uno de los comandos de administración de Django. Este no es el tipo de almacenamiento en caché de mayor rendimiento, pero puede ser útil para almacenar consultas de bases de datos complejas.
  3. Sistema de archivos : el caché se guarda en el sistema de archivos, en archivos separados para cada valor de caché. Este es el más lento de todos los tipos de almacenamiento en caché, pero es el más fácil de configurar en un entorno de producción.
  4. Memoria local: caché de memoria local, que es la más adecuada para sus entornos locales de desarrollo o prueba. Si bien es casi tan rápido como Memcached, no puede escalar más allá de un solo servidor, por lo que no es apropiado usarlo como caché de datos para ninguna aplicación que use más de un servidor web.
  5. Dummy : un caché "ficticio" que en realidad no almacena nada en caché pero aún implementa la interfaz de caché. Está destinado a ser utilizado en desarrollo o prueba cuando no desea almacenar en caché, pero no desea cambiar su código.

Niveles de almacenamiento en caché de Django

El almacenamiento en caché en Django se puede implementar en diferentes niveles (o partes del sitio). Puede almacenar en caché todo el sitio o partes específicas con varios niveles de granularidad (enumerados en orden descendente de granularidad):

Caché por sitio

Esta es la forma más fácil de implementar el almacenamiento en caché en Django. Para hacer esto, todo lo que tendrá que hacer es agregar dos clases de middleware a su archivo settings.py :

MIDDLEWARE = [
    'django.middleware.cache.UpdateCacheMiddleware',     # NEW
    'django.middleware.common.CommonMiddleware',
    'django.middleware.cache.FetchFromCacheMiddleware',  # NEW
]

El orden del middleware es importante aquí. UpdateCacheMiddlewaredebe venir antes FetchFromCacheMiddleware. Para obtener más información, consulte Orden de MIDDLEWARE de los documentos de Django.

A continuación, debe agregar la siguiente configuración:

CACHE_MIDDLEWARE_ALIAS = 'default'  # which cache alias to use
CACHE_MIDDLEWARE_SECONDS = '600'    # number of seconds to cache a page for (TTL)
CACHE_MIDDLEWARE_KEY_PREFIX = ''    # should be used if the cache is shared across multiple sites that use the same Django instance

Si bien el almacenamiento en caché de todo el sitio podría ser una buena opción si su sitio tiene poco o ningún contenido dinámico, puede no ser apropiado para sitios grandes con un servidor de caché basado en memoria, ya que la memoria RAM es, bueno, costosa.

Caché por vista

En lugar de desperdiciar un valioso espacio de memoria almacenando en caché páginas estáticas o páginas dinámicas que obtienen datos de una API que cambia rápidamente, puede almacenar en caché vistas específicas. Este es el enfoque que usaremos en este artículo. También es el nivel de almacenamiento en caché con el que casi siempre debe comenzar cuando busca implementar el almacenamiento en caché en su aplicación Django.

Puede implementar este tipo de caché con el decorador cache_page ya sea en la función de vista directamente o en la ruta dentro de URLConf:

from django.views.decorators.cache import cache_page

@cache_page(60 * 15)
def your_view(request):
    ...

# or

from django.views.decorators.cache import cache_page

urlpatterns = [
    path('object/<int:object_id>/', cache_page(60 * 15)(your_view)),
]

El caché en sí se basa en la URL, por lo que las solicitudes a, digamos, object/1y object/2se almacenarán en caché por separado.

Vale la pena señalar que implementar el caché directamente en la vista hace que sea más difícil deshabilitar el caché en ciertas situaciones. Por ejemplo, ¿qué sucede si desea permitir que ciertos usuarios accedan a la vista sin el caché? Habilitar el caché a través de URLConfbrinda la oportunidad de asociar una URL diferente a la vista que no usa el caché:

from django.views.decorators.cache import cache_page

urlpatterns = [
    path('object/<int:object_id>/', your_view),
    path('object/cache/<int:object_id>/', cache_page(60 * 15)(your_view)),
]

Caché de fragmentos de plantilla

Si sus plantillas contienen partes que cambian con frecuencia en función de los datos, probablemente querrá dejarlas fuera de la memoria caché.

Por ejemplo, tal vez use el correo electrónico del usuario autenticado en la barra de navegación en un área de la plantilla. Bueno, si tiene miles de usuarios, ese fragmento se duplicará miles de veces en la RAM, uno para cada usuario. Aquí es donde entra en juego el almacenamiento en caché de fragmentos de plantilla, que le permite especificar las áreas específicas de una plantilla para almacenar en caché.

Para almacenar en caché una lista de objetos:

{% load cache %}

{% cache 500 object_list %}
  <ul>
    {% for object in objects %}
      <li>{{ object.title }}</li>
    {% endfor %}
  </ul>
{% endcache %}

Aquí, {% load cache %}nos da acceso a la cacheetiqueta de plantilla, que espera un tiempo de espera de caché en segundos ( 500) junto con el nombre del fragmento de caché ( object_list).

API de caché de bajo nivel

Para los casos en los que las opciones anteriores no brindan suficiente granularidad, puede usar la API de bajo nivel para administrar objetos individuales en el caché por clave de caché.

Por ejemplo:

from django.core.cache import cache


def get_context_data(self, **kwargs):
    context = super().get_context_data(**kwargs)

    objects = cache.get('objects')

    if objects is None:
        objects = Objects.all()
        cache.set('objects', objects)

    context['objects'] = objects

    return context

En este ejemplo, deseará invalidar (o eliminar) la memoria caché cuando se agreguen, cambien o eliminen objetos de la base de datos. Una forma de gestionar esto es a través de las señales de la base de datos:

from django.core.cache import cache
from django.db.models.signals import post_delete, post_save
from django.dispatch import receiver


@receiver(post_delete, sender=Object)
def object_post_delete_handler(sender, **kwargs):
     cache.delete('objects')


@receiver(post_save, sender=Object)
def object_post_save_handler(sender, **kwargs):
    cache.delete('objects')

Para obtener más información sobre el uso de señales de bases de datos para invalidar la memoria caché, consulte el artículo API de caché de bajo nivel en Django .

Con eso, veamos algunos ejemplos.

Configuración del proyecto

Clona el proyecto base del repositorio cache-django-view y luego revisa la rama base:

$ git clone https://github.com/testdrivenio/cache-django-view.git --branch base --single-branch
$ cd cache-django-view

Cree (y active) un entorno virtual e instale los requisitos:

$ python3.9 -m venv venv
$ source venv/bin/activate
(venv)$ pip install -r requirements.txt

Aplique las migraciones de Django y luego inicie el servidor:

(venv)$ python manage.py migrate
(venv)$ python manage.py runserver

Navegue a http://127.0.0.1:8000 en su navegador de elección para asegurarse de que todo funcione como se espera.

Debería ver:

página web sin caché

Toma nota de tu terminal. Debería ver el tiempo total de ejecución de la solicitud:

Total time: 2.23s

Esta métrica proviene de core/middleware.py :

import logging
import time


def metric_middleware(get_response):
    def middleware(request):
        # Get beginning stats
        start_time = time.perf_counter()

        # Process the request
        response = get_response(request)

        # Get ending stats
        end_time = time.perf_counter()

        # Calculate stats
        total_time = end_time - start_time

        # Log the results
        logger = logging.getLogger('debug')
        logger.info(f'Total time: {(total_time):.2f}s')
        print(f'Total time: {(total_time):.2f}s')

        return response

    return middleware

Eche un vistazo rápido a la vista en apicalls/views.py :

import datetime

import requests
from django.views.generic import TemplateView


BASE_URL = 'https://httpbin.org/'


class ApiCalls(TemplateView):
    template_name = 'apicalls/home.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        response = requests.get(f'{BASE_URL}/delay/2')
        response.raise_for_status()
        context['content'] = 'Results received!'
        context['current_time'] = datetime.datetime.now()
        return context

Esta vista realiza una llamada HTTP requestsa httpbin.org . Para simular una solicitud larga, la respuesta de la API se retrasa dos segundos. Por lo tanto, http://127.0.0.1:8000 debería tardar unos dos segundos en procesarse no solo en la solicitud inicial, sino también en cada solicitud posterior. Si bien una carga de dos segundos es algo aceptable en la solicitud inicial, es completamente inaceptable para las solicitudes posteriores, ya que los datos no cambian. Arreglemos esto almacenando en caché toda la vista usando el nivel de caché por vista de Django.

Flujo de trabajo:

  1. Realice una llamada HTTP completa a httpbin.org en la solicitud inicial
  2. Almacenar en caché la vista
  3. Las solicitudes posteriores se extraerán de la memoria caché, sin pasar por la llamada HTTP.
  4. Invalidar el caché después de un período de tiempo (TTL)

Punto de referencia de rendimiento de referencia

Antes de agregar la memoria caché, ejecutemos rápidamente una prueba de carga para obtener una referencia de referencia con Apache Bench , para tener una idea aproximada de cuántas solicitudes puede manejar nuestra aplicación por segundo.

Apache Bench viene preinstalado en Mac.

Si está en un sistema Linux, es probable que ya esté instalado y listo para funcionar también. Si no, puede instalar a través de APT ( apt-get install apache2-utils) o YUM ( yum install httpd-tools).

Los usuarios de Windows deberán descargar y extraer los archivos binarios de Apache.

Agregue Gunicorn al archivo de requisitos:

gunicorn==20.1.0

Elimine el servidor de desarrollo de Django e instale Gunicorn:

(venv)$ pip install -r requirements.txt

A continuación, sirva la aplicación Django con Gunicorn (y cuatro trabajadores ) así:

(venv)$ gunicorn core.wsgi:application -w 4

En una nueva ventana de terminal, ejecute Apache Bench:

$ ab -n 100 -c 10 http://127.0.0.1:8000/

Esto simulará 100 conexiones en 10 subprocesos simultáneos. Son 100 solicitudes, 10 a la vez.

Toma nota de las peticiones por segundo:

Requests per second:    1.69 [#/sec] (mean)

Tenga en cuenta que la barra de herramientas de depuración de Django agregará un poco de sobrecarga. La evaluación comparativa en general es difícil de acertar perfectamente. Lo importante es la constancia. Elija una métrica en la que centrarse y utilice el mismo entorno para cada prueba.

Elimine el servidor de Gunicorn y vuelva a hacer girar el servidor de desarrollo de Django:

(venv)$ python manage.py runserver

Con eso, veamos cómo almacenar en caché una vista.

Almacenamiento en caché de una vista

Comience decorando la ApiCallsvista con el @cache_pagedecorador así:

import datetime

import requests
from django.utils.decorators import method_decorator # NEW
from django.views.decorators.cache import cache_page # NEW
from django.views.generic import TemplateView


BASE_URL = 'https://httpbin.org/'


@method_decorator(cache_page(60 * 5), name='dispatch') # NEW
class ApiCalls(TemplateView):
    template_name = 'apicalls/home.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        response = requests.get(f'{BASE_URL}/delay/2')
        response.raise_for_status()
        context['content'] = 'Results received!'
        context['current_time'] = datetime.datetime.now()
        return context

Como estamos usando una vista basada en clases, no podemos poner el decorador directamente en la clase, así que usamos un method_decoratory especificado dispatch(como el método que se va a decorar) para el argumento del nombre.

La caché de este ejemplo establece un tiempo de espera (o TTL) de cinco minutos.

Alternativamente, puede configurar esto en su configuración de esta manera:

# Cache time to live is 5 minutes
CACHE_TTL = 60 * 5

Luego, de vuelta en la vista:

import datetime

import requests
from django.conf import settings
from django.core.cache.backends.base import DEFAULT_TIMEOUT
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page
from django.views.generic import TemplateView

BASE_URL = 'https://httpbin.org/'
CACHE_TTL = getattr(settings, 'CACHE_TTL', DEFAULT_TIMEOUT)


@method_decorator(cache_page(CACHE_TTL), name='dispatch')
class ApiCalls(TemplateView):
    template_name = 'apicalls/home.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        response = requests.get(f'{BASE_URL}/delay/2')
        response.raise_for_status()
        context['content'] = 'Results received!'
        context['current_time'] = datetime.datetime.now()
        return context

A continuación, agreguemos un backend de caché.

Redis contra Memcached

Memcached y Redis son almacenes de datos clave-valor en memoria. Son fáciles de usar y están optimizados para búsquedas de alto rendimiento. Probablemente no verá mucha diferencia en el rendimiento o el uso de la memoria entre los dos. Dicho esto, Memcached es un poco más fácil de configurar ya que está diseñado para ser simple y fácil de usar. Redis, por otro lado, tiene un conjunto más rico de funciones, por lo que tiene una amplia gama de casos de uso más allá del almacenamiento en caché. Por ejemplo, a menudo se usa para almacenar sesiones de usuario o como intermediario de mensajes en un sistema pub/sub. Debido a su flexibilidad, a menos que ya haya invertido en Memcached, Redis es una solución mucho mejor.

Para obtener más información sobre esto, revise esta respuesta de desbordamiento de pila.

A continuación, elija el almacén de datos de su elección y veamos cómo almacenar en caché una vista.

Opción 1: Redis con Django

Descargue e instale Redis.

Si está en una Mac, le recomendamos que instale Redis con Homebrew :

$ preparar instalar redis

Una vez instalado, en una nueva ventana de terminal, inicie el servidor Redis y asegúrese de que se esté ejecutando en su puerto predeterminado, 6379. El número de puerto será importante cuando le digamos a Django cómo comunicarse con Redis.

$ redis-server

Para que Django use Redis como backend de caché, primero debemos instalar django-redis .

Agréguelo al archivo requirements.txt :

django-redis==5.0.0

Instalar:

(venv)$ pip install -r requirements.txt

A continuación, agregue el backend personalizado al archivo settings.py :

CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': 'redis://127.0.0.1:6379/1',
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient',
        }
    }
}

Ahora, cuando vuelva a ejecutar el servidor, Redis se usará como backend de caché:

(venv)$ python manage.py runserver

Con el servidor en funcionamiento, vaya a http://127.0.0.1:8000 .

La primera solicitud aún tardará unos dos segundos. Recarga la página. La página debería cargarse casi instantáneamente. Echa un vistazo al tiempo de carga en tu terminal. Debe estar cerca de cero:

Total time: 0.01s

¿Tiene curiosidad por saber cómo se ven los datos almacenados en caché dentro de Redis?

Ejecute Redis CLI en modo interactivo en una nueva ventana de terminal:

$ redis-cli

Debería ver:

127.0.0.1:6379>

Ejecutar pingpara asegurarse de que todo funciona correctamente:

127.0.0.1:6379> ping
PONG

Regrese al archivo de configuración. Utilizamos la base de datos Redis número 1: 'LOCATION': 'redis://127.0.0.1:6379/1',. Entonces, ejecute select 1para seleccionar esa base de datos y luego ejecute keys *para ver todas las claves:

127.0.0.1:6379> select 1
OK

127.0.0.1:6379[1]> keys *
1) ":1:views.decorators.cache.cache_header..17abf5259517d604cc9599a00b7385d6.en-us.UTC"
2) ":1:views.decorators.cache.cache_page..GET.17abf5259517d604cc9599a00b7385d6.d41d8cd98f00b204e9800998ecf8427e.en-us.UTC"

Podemos ver que Django puso una clave de encabezado y una cache_pageclave.

Para ver los datos almacenados en caché reales, ejecute el getcomando con la clave como argumento:

127.0.0.1:6379[1]> get ":1:views.decorators.cache.cache_page..GET.17abf5259517d604cc9599a00b7385d6.d41d8cd98f00b204e9800998ecf8427e.en-us.UTC"

Deberías ver algo similar a:

"\x80\x05\x95D\x04\x00\x00\x00\x00\x00\x00\x8c\x18django.template.response\x94\x8c\x10TemplateResponse
\x94\x93\x94)\x81\x94}\x94(\x8c\x05using\x94N\x8c\b_headers\x94}\x94(\x8c\x0ccontent-type\x94\x8c\
x0cContent-Type\x94\x8c\x18text/html; charset=utf-8\x94\x86\x94\x8c\aexpires\x94\x8c\aExpires\x94\x8c\x1d
Fri, 01 May 2020 13:36:59 GMT\x94\x86\x94\x8c\rcache-control\x94\x8c\rCache-Control\x94\x8c\x0
bmax-age=300\x94\x86\x94u\x8c\x11_resource_closers\x94]\x94\x8c\x0e_handler_class\x94N\x8c\acookies
\x94\x8c\x0chttp.cookies\x94\x8c\x0cSimpleCookie\x94\x93\x94)\x81\x94\x8c\x06closed\x94\x89\x8c
\x0e_reason_phrase\x94N\x8c\b_charset\x94N\x8c\n_container\x94]\x94B\xaf\x02\x00\x00
<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Home</title>\n
<link rel=\"stylesheet\" href=\"https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css\
"\n          integrity=\"sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh\"
crossorigin=\"anonymous\">\n\n</head>\n<body>\n<div class=\"container\">\n    <div class=\"pt-3\">\n
<h1>Below is the result of the APICall</h1>\n    </div>\n    <div class=\"pt-3 pb-3\">\n
<a href=\"/\">\n            <button type=\"button\" class=\"btn btn-success\">\n
Get new data\n            </button>\n        </a>\n    </div>\n    Results received!<br>\n
13:31:59\n</div>\n</body>\n</html>\x94a\x8c\x0c_is_rendered\x94\x88ub."

Salga de la CLI interactiva una vez hecho:

127.0.0.1:6379[1]> exit

Pase a la sección "Pruebas de rendimiento".

Opción 2: Memcached con Django

Comience agregando pymemcache al archivo requirements.txt :

pymemcache==3.5.0

Instala las dependencias:

(venv)$ pip install -r requirements.txt

A continuación, debemos actualizar la configuración en core/settings.py para habilitar el backend de Memcached:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
        'LOCATION': '127.0.0.1:11211',
    }
}

Aquí, agregamos el backend de PyMemcacheCache e indicamos que Memcached debería estar ejecutándose en nuestra máquina local en el puerto localhost (127.0.0.1) 11211, que es el puerto predeterminado para Memcached.

A continuación, debemos instalar y ejecutar el demonio Memcached. La forma más fácil de instalarlo es a través de un administrador de paquetes como APT, YUM, Homebrew o Chocolatey, según su sistema operativo:

# linux
$ apt-get install memcached
$ yum install memcached

# mac
$ brew install memcached

# windows
$ choco install memcached

Luego, ejecútelo en una terminal diferente en el puerto 11211:

$ memcached -p 11211

# test: telnet localhost 11211

Para obtener más información sobre la instalación y configuración de Memcached, consulte la wiki oficial .

Navegue a http://127.0.0.1:8000 en nuestro navegador nuevamente. La primera solicitud aún tomará los dos segundos completos, pero todas las solicitudes posteriores aprovecharán el caché. Entonces, si actualiza o presiona el botón "Obtener nuevos datos", la página debería cargarse casi instantáneamente.

¿Cómo es el tiempo de ejecución en tu terminal?

Total time: 0.03s

Pruebas de rendimiento

Si observamos el tiempo que se tarda en cargar la primera solicitud frente a la segunda solicitud (almacenada en caché) en la barra de herramientas de depuración de Django, se verá similar a:

tiempo de carga sin caché

tiempo de carga con caché

También en la barra de herramientas de depuración, puede ver las operaciones de caché:

operaciones de caché

Vuelva a girar Gunicorn y vuelva a ejecutar las pruebas de rendimiento:

$ ab -n 100 -c 10 http://127.0.0.1:8000/

¿Cuáles son las nuevas solicitudes por segundo? ¡Son alrededor de 36 en mi máquina!

Conclusión

En este artículo, analizamos las diferentes opciones integradas para el almacenamiento en caché en Django, así como los diferentes niveles de almacenamiento en caché disponibles. También detallamos cómo almacenar en caché una vista usando el caché por vista de Django con Memcached y Redis.

Puede encontrar el código final para ambas opciones, Memcached y Redis, en el repositorio cache-django-view .

--

En general, querrá buscar el almacenamiento en caché cuando la representación de la página sea lenta debido a la latencia de la red de las consultas de la base de datos o las llamadas HTTP.

A partir de ahí, se recomienda encarecidamente utilizar un backend de caché de Django personalizado con Redis con un Per-viewtipo. Si necesita más granularidad y control, porque no todos los datos en la plantilla son los mismos para todos los usuarios o partes de los datos cambian con frecuencia, vaya a la caché de fragmentos de plantilla o a la API de caché de bajo nivel.

Fuente:  https://testdriven.io

#django #caching 

Aprenda Sobre El Almacenamiento En Caché En Django
Neil  Morgan

Neil Morgan

1660870260

Aprenda Sobre Cache No Django

O armazenamento em cache geralmente é a maneira mais eficaz de aumentar o desempenho de um aplicativo.

Para sites dinâmicos, ao renderizar um modelo, muitas vezes você terá que coletar dados de várias fontes (como um banco de dados, o sistema de arquivos e APIs de terceiros, para citar alguns), processar os dados e aplicar lógica de negócios a antes de servi-lo a um cliente. Qualquer atraso devido à latência da rede será notado pelo usuário final.

Por exemplo, digamos que você precise fazer uma chamada HTTP para uma API externa para obter os dados necessários para renderizar um modelo. Mesmo em condições perfeitas, isso aumentará o tempo de renderização, o que aumentará o tempo total de carregamento. E se a API cair ou você estiver sujeito a limitação de taxa? De qualquer forma, se os dados forem atualizados com pouca frequência, é uma boa ideia implementar um mecanismo de cache para evitar a necessidade de fazer a chamada HTTP para cada solicitação do cliente.

Este artigo analisa como fazer exatamente isso, primeiro revisando a estrutura de cache do Django como um todo e, em seguida, detalhando passo a passo como armazenar em cache uma visualização do Django.

Dependências:

  1. Django v3.2.5
  2. django-redis v5.0.0
  3. Python v3.9.6
  4. pymemcache v3.5.0
  5. Solicitações v2.26.0

Objetivos

Ao final deste tutorial, você deverá ser capaz de:

  1. Explique por que você pode querer considerar o cache de uma visualização do Django
  2. Descrever as opções internas do Django disponíveis para armazenamento em cache
  3. Cache de uma visualização do Django com o Redis
  4. Teste de carga de um aplicativo Django com o Apache Bench
  5. Cache de uma visualização do Django com o Memcached

Tipos de cache do Django

O Django vem com vários backends de cache integrados , bem como suporte para um backend personalizado .

As opções incorporadas são:

  1. Memcached : Memcached é um armazenamento de valor-chave baseado em memória para pequenos blocos de dados. Ele suporta cache distribuído em vários servidores.
  2. Banco de dados : Aqui, os fragmentos de cache são armazenados em um banco de dados. Uma tabela para esse propósito pode ser criada com um dos comandos admin do Django. Esse não é o tipo de cache com melhor desempenho, mas pode ser útil para armazenar consultas de banco de dados complexas.
  3. Sistema de arquivos : O cache é salvo no sistema de arquivos, em arquivos separados para cada valor de cache. Este é o mais lento de todos os tipos de cache, mas é o mais fácil de configurar em um ambiente de produção.
  4. Memória local : cache de memória local, mais adequado para seus ambientes locais de desenvolvimento ou teste. Embora seja quase tão rápido quanto o Memcached, ele não pode ser dimensionado além de um único servidor, portanto, não é apropriado usá-lo como cache de dados para qualquer aplicativo que use mais de um servidor web.
  5. Dummy : Um cache "dummy" que na verdade não armazena nada em cache, mas ainda implementa a interface de cache. Ele deve ser usado em desenvolvimento ou teste quando você não deseja armazenar em cache, mas não deseja alterar seu código.

Níveis de cache do Django

O cache no Django pode ser implementado em diferentes níveis (ou partes do site). Você pode armazenar em cache todo o site ou partes específicas com vários níveis de granularidade (listados em ordem decrescente de granularidade):

Cache por site

Esta é a maneira mais fácil de implementar o cache no Django. Para fazer isso, tudo o que você precisa fazer é adicionar duas classes de middleware ao seu arquivo settings.py :

MIDDLEWARE = [
    'django.middleware.cache.UpdateCacheMiddleware',     # NEW
    'django.middleware.common.CommonMiddleware',
    'django.middleware.cache.FetchFromCacheMiddleware',  # NEW
]

A ordem do middleware é importante aqui. UpdateCacheMiddlewaredeve vir antes FetchFromCacheMiddleware. Para mais informações, dê uma olhada em Order of MIDDLEWARE dos documentos do Django.

Em seguida, você precisa adicionar as seguintes configurações:

CACHE_MIDDLEWARE_ALIAS = 'default'  # which cache alias to use
CACHE_MIDDLEWARE_SECONDS = '600'    # number of seconds to cache a page for (TTL)
CACHE_MIDDLEWARE_KEY_PREFIX = ''    # should be used if the cache is shared across multiple sites that use the same Django instance

Embora o cache de todo o site possa ser uma boa opção se o seu site tiver pouco ou nenhum conteúdo dinâmico, pode não ser apropriado usá-lo para sites grandes com um back-end de cache baseado em memória, pois a RAM é muito cara.

Cache por visualização

Em vez de desperdiçar um precioso espaço de memória no cache de páginas estáticas ou páginas dinâmicas que originam dados de uma API que muda rapidamente, você pode armazenar em cache visualizações específicas. Essa é a abordagem que usaremos neste artigo. É também o nível de cache com o qual você quase sempre deve começar ao tentar implementar o cache em seu aplicativo Django.

Você pode implementar esse tipo de cache com o decorador cache_page diretamente na função de visualização ou no caminho dentro de URLConf:

from django.views.decorators.cache import cache_page

@cache_page(60 * 15)
def your_view(request):
    ...

# or

from django.views.decorators.cache import cache_page

urlpatterns = [
    path('object/<int:object_id>/', cache_page(60 * 15)(your_view)),
]

O cache em si é baseado na URL, portanto, solicitações para, digamos, object/1e object/2serão armazenadas em cache separadamente.

Vale a pena notar que implementar o cache diretamente na visualização torna mais difícil desabilitar o cache em determinadas situações. Por exemplo, e se você quiser permitir que determinados usuários acessem a visualização sem o cache? Habilitar o cache por meio do URLConffornece a oportunidade de associar uma URL diferente à visualização que não usa o cache:

from django.views.decorators.cache import cache_page

urlpatterns = [
    path('object/<int:object_id>/', your_view),
    path('object/cache/<int:object_id>/', cache_page(60 * 15)(your_view)),
]

Cache de fragmento de modelo

Se seus modelos contiverem partes que mudam frequentemente com base nos dados, você provavelmente desejará deixá-las fora do cache.

Por exemplo, talvez você use o email do usuário autenticado na barra de navegação em uma área do modelo. Bem, se você tiver milhares de usuários, esse fragmento será duplicado milhares de vezes na RAM, uma para cada usuário. É aqui que o cache de fragmentos de modelo entra em ação, o que permite especificar as áreas específicas de um modelo para armazenar em cache.

Para armazenar em cache uma lista de objetos:

{% load cache %}

{% cache 500 object_list %}
  <ul>
    {% for object in objects %}
      <li>{{ object.title }}</li>
    {% endfor %}
  </ul>
{% endcache %}

Aqui, {% load cache %}nos dá acesso à cachetag template, que espera um tempo limite de cache em segundos ( 500) junto com o nome do fragmento de cache ( object_list).

API de cache de baixo nível

Para casos em que as opções anteriores não fornecem granularidade suficiente, você pode usar a API de baixo nível para gerenciar objetos individuais no cache por chave de cache.

Por exemplo:

from django.core.cache import cache


def get_context_data(self, **kwargs):
    context = super().get_context_data(**kwargs)

    objects = cache.get('objects')

    if objects is None:
        objects = Objects.all()
        cache.set('objects', objects)

    context['objects'] = objects

    return context

Neste exemplo, você desejará invalidar (ou remover) o cache quando os objetos forem adicionados, alterados ou removidos do banco de dados. Uma maneira de gerenciar isso é por meio de sinais de banco de dados:

from django.core.cache import cache
from django.db.models.signals import post_delete, post_save
from django.dispatch import receiver


@receiver(post_delete, sender=Object)
def object_post_delete_handler(sender, **kwargs):
     cache.delete('objects')


@receiver(post_save, sender=Object)
def object_post_save_handler(sender, **kwargs):
    cache.delete('objects')

Para saber mais sobre como usar sinais de banco de dados para invalidar o cache, confira o artigo API de cache de baixo nível no Django .

Com isso, vejamos alguns exemplos.

Configuração do projeto

Clone o projeto base do repositório cache-django-view e, em seguida, confira o branch base:

$ git clone https://github.com/testdrivenio/cache-django-view.git --branch base --single-branch
$ cd cache-django-view

Crie (e ative) um ambiente virtual e instale os requisitos:

$ python3.9 -m venv venv
$ source venv/bin/activate
(venv)$ pip install -r requirements.txt

Aplique as migrações do Django e inicie o servidor:

(venv)$ python manage.py migrate
(venv)$ python manage.py runserver

Navegue até http://127.0.0.1:8000 no navegador de sua preferência para garantir que tudo funcione conforme o esperado.

Você deveria ver:

página da web sem cache

Tome nota do seu terminal. Você deve ver o tempo total de execução da solicitação:

Total time: 2.23s

Esta métrica vem de core/middleware.py :

import logging
import time


def metric_middleware(get_response):
    def middleware(request):
        # Get beginning stats
        start_time = time.perf_counter()

        # Process the request
        response = get_response(request)

        # Get ending stats
        end_time = time.perf_counter()

        # Calculate stats
        total_time = end_time - start_time

        # Log the results
        logger = logging.getLogger('debug')
        logger.info(f'Total time: {(total_time):.2f}s')
        print(f'Total time: {(total_time):.2f}s')

        return response

    return middleware

Dê uma olhada rápida na visualização em apicalls/views.py :

import datetime

import requests
from django.views.generic import TemplateView


BASE_URL = 'https://httpbin.org/'


class ApiCalls(TemplateView):
    template_name = 'apicalls/home.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        response = requests.get(f'{BASE_URL}/delay/2')
        response.raise_for_status()
        context['content'] = 'Results received!'
        context['current_time'] = datetime.datetime.now()
        return context

Essa visualização faz uma chamada HTTP com requestspara httpbin.org . Para simular uma solicitação longa, a resposta da API é atrasada por dois segundos. Portanto, deve levar cerca de dois segundos para http://127.0.0.1:8000 renderizar não apenas na solicitação inicial, mas também em cada solicitação subsequente. Embora um carregamento de dois segundos seja aceitável na solicitação inicial, é completamente inaceitável para solicitações subsequentes, pois os dados não são alterados. Vamos corrigir isso armazenando em cache a visão inteira usando o nível de cache Per-view do Django.

Fluxo de trabalho:

  1. Faça uma chamada HTTP completa para httpbin.org na solicitação inicial
  2. Armazenar a visualização em cache
  3. As solicitações subsequentes serão extraídas do cache, ignorando a chamada HTTP
  4. Invalidar o cache após um período de tempo (TTL)

Referência de desempenho de linha de base

Antes de adicionar o cache, vamos executar rapidamente um teste de carga para obter uma linha de base de referência usando o Apache Bench , para ter uma ideia aproximada de quantas solicitações nosso aplicativo pode manipular por segundo.

O Apache Bench vem pré-instalado no Mac.

Se você estiver em um sistema Linux, é provável que ele já esteja instalado e pronto para funcionar também. Caso contrário, você pode instalar via APT ( apt-get install apache2-utils) ou YUM ( yum install httpd-tools).

Os usuários do Windows precisarão baixar e extrair os binários do Apache.

Adicione Gunicorn ao arquivo de requisitos:

gunicorn==20.1.0

Mate o servidor de desenvolvimento do Django e instale o Gunicorn:

(venv)$ pip install -r requirements.txt

Em seguida, sirva o aplicativo Django com Gunicorn (e quatro workers ) assim:

(venv)$ gunicorn core.wsgi:application -w 4

Em uma nova janela de terminal, execute o Apache Bench:

$ ab -n 100 -c 10 http://127.0.0.1:8000/

Isso simulará 100 conexões em 10 threads simultâneos. São 100 solicitações, 10 de cada vez.

Anote as solicitações por segundo:

Requests per second:    1.69 [#/sec] (mean)

Tenha em mente que a barra de ferramentas de depuração do Django adicionará um pouco de sobrecarga. O benchmarking em geral é difícil de acertar perfeitamente. O importante é a consistência. Escolha uma métrica para focar e use o mesmo ambiente para cada teste.

Mate o servidor Gunicorn e gire o servidor de desenvolvimento Django de volta:

(venv)$ python manage.py runserver

Com isso, vamos ver como armazenar em cache uma visualização.

Como armazenar em cache uma visualização

Comece decorando a ApiCallsvista com o @cache_pagedecorador assim:

import datetime

import requests
from django.utils.decorators import method_decorator # NEW
from django.views.decorators.cache import cache_page # NEW
from django.views.generic import TemplateView


BASE_URL = 'https://httpbin.org/'


@method_decorator(cache_page(60 * 5), name='dispatch') # NEW
class ApiCalls(TemplateView):
    template_name = 'apicalls/home.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        response = requests.get(f'{BASE_URL}/delay/2')
        response.raise_for_status()
        context['content'] = 'Results received!'
        context['current_time'] = datetime.datetime.now()
        return context

Como estamos usando uma visão baseada em classe, não podemos colocar o decorador diretamente na classe, então usamos a method_decoratore especificamos dispatch(como o método a ser decorado) para o argumento name.

O cache neste exemplo define um tempo limite (ou TTL) de cinco minutos.

Alternativamente, você pode definir isso em suas configurações assim:

# Cache time to live is 5 minutes
CACHE_TTL = 60 * 5

Então, de volta na visão:

import datetime

import requests
from django.conf import settings
from django.core.cache.backends.base import DEFAULT_TIMEOUT
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page
from django.views.generic import TemplateView

BASE_URL = 'https://httpbin.org/'
CACHE_TTL = getattr(settings, 'CACHE_TTL', DEFAULT_TIMEOUT)


@method_decorator(cache_page(CACHE_TTL), name='dispatch')
class ApiCalls(TemplateView):
    template_name = 'apicalls/home.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        response = requests.get(f'{BASE_URL}/delay/2')
        response.raise_for_status()
        context['content'] = 'Results received!'
        context['current_time'] = datetime.datetime.now()
        return context

Em seguida, vamos adicionar um back-end de cache.

Redis x Memcached

O Memcached e o Redis são armazenamentos de dados de valor-chave na memória. Eles são fáceis de usar e otimizados para pesquisas de alto desempenho. Você provavelmente não verá muita diferença no desempenho ou no uso de memória entre os dois. Dito isso, o Memcached é um pouco mais fácil de configurar, pois foi projetado para simplicidade e facilidade de uso. O Redis, por outro lado, possui um conjunto mais rico de recursos, portanto, possui uma ampla variedade de casos de uso além do armazenamento em cache. Por exemplo, geralmente é usado para armazenar sessões de usuário ou como agente de mensagens em um sistema pub/sub. Devido à sua flexibilidade, a menos que você já tenha investido no Memcached, o Redis é uma solução muito melhor.

Para saber mais sobre isso, revise esta resposta do Stack Overflow.

Em seguida, escolha o armazenamento de dados de sua preferência e vejamos como armazenar em cache uma visualização.

Opção 1: Redis com Django

Baixe e instale o Redis.

Se você estiver em um Mac, recomendamos instalar o Redis com Homebrew :

$ brew instalar redis

Uma vez instalado, em uma nova janela de terminal, inicie o servidor Redis e verifique se ele está rodando em sua porta padrão, 6379. O número da porta será importante quando dissermos ao Django como se comunicar com o Redis.

$ redis-server

Para o Django usar o Redis como um backend de cache, primeiro precisamos instalar o django-redis .

Adicione-o ao arquivo requirements.txt :

django-redis==5.0.0

Instalar:

(venv)$ pip install -r requirements.txt

Em seguida, adicione o back-end personalizado ao arquivo settings.py :

CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': 'redis://127.0.0.1:6379/1',
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient',
        }
    }
}

Agora, quando você executar o servidor novamente, o Redis será usado como back-end de cache:

(venv)$ python manage.py runserver

Com o servidor funcionando, navegue até http://127.0.0.1:8000 .

A primeira solicitação ainda levará cerca de dois segundos. Recarregue a página. A página deve carregar quase instantaneamente. Dê uma olhada no tempo de carregamento em seu terminal. Deve ser próximo de zero:

Total time: 0.01s

Curioso como são os dados armazenados em cache dentro do Redis?

Execute o Redis CLI no modo interativo em uma nova janela de terminal:

$ redis-cli

Você deveria ver:

127.0.0.1:6379>

Execute pingpara garantir que tudo funcione corretamente:

127.0.0.1:6379> ping
PONG

Volte para o arquivo de configurações. Usamos o banco de dados Redis número 1: 'LOCATION': 'redis://127.0.0.1:6379/1',. Então, execute select 1para selecionar esse banco de dados e, em seguida, execute keys *para visualizar todas as chaves:

127.0.0.1:6379> select 1
OK

127.0.0.1:6379[1]> keys *
1) ":1:views.decorators.cache.cache_header..17abf5259517d604cc9599a00b7385d6.en-us.UTC"
2) ":1:views.decorators.cache.cache_page..GET.17abf5259517d604cc9599a00b7385d6.d41d8cd98f00b204e9800998ecf8427e.en-us.UTC"

Podemos ver que o Django colocou uma chave de cabeçalho e uma cache_pagechave.

Para visualizar os dados reais em cache, execute o getcomando com a chave como argumento:

127.0.0.1:6379[1]> get ":1:views.decorators.cache.cache_page..GET.17abf5259517d604cc9599a00b7385d6.d41d8cd98f00b204e9800998ecf8427e.en-us.UTC"

Você deve ver algo semelhante a:

"\x80\x05\x95D\x04\x00\x00\x00\x00\x00\x00\x8c\x18django.template.response\x94\x8c\x10TemplateResponse
\x94\x93\x94)\x81\x94}\x94(\x8c\x05using\x94N\x8c\b_headers\x94}\x94(\x8c\x0ccontent-type\x94\x8c\
x0cContent-Type\x94\x8c\x18text/html; charset=utf-8\x94\x86\x94\x8c\aexpires\x94\x8c\aExpires\x94\x8c\x1d
Fri, 01 May 2020 13:36:59 GMT\x94\x86\x94\x8c\rcache-control\x94\x8c\rCache-Control\x94\x8c\x0
bmax-age=300\x94\x86\x94u\x8c\x11_resource_closers\x94]\x94\x8c\x0e_handler_class\x94N\x8c\acookies
\x94\x8c\x0chttp.cookies\x94\x8c\x0cSimpleCookie\x94\x93\x94)\x81\x94\x8c\x06closed\x94\x89\x8c
\x0e_reason_phrase\x94N\x8c\b_charset\x94N\x8c\n_container\x94]\x94B\xaf\x02\x00\x00
<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Home</title>\n
<link rel=\"stylesheet\" href=\"https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css\
"\n          integrity=\"sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh\"
crossorigin=\"anonymous\">\n\n</head>\n<body>\n<div class=\"container\">\n    <div class=\"pt-3\">\n
<h1>Below is the result of the APICall</h1>\n    </div>\n    <div class=\"pt-3 pb-3\">\n
<a href=\"/\">\n            <button type=\"button\" class=\"btn btn-success\">\n
Get new data\n            </button>\n        </a>\n    </div>\n    Results received!<br>\n
13:31:59\n</div>\n</body>\n</html>\x94a\x8c\x0c_is_rendered\x94\x88ub."

Saia da CLI interativa uma vez feito:

127.0.0.1:6379[1]> exit

Pule para a seção "Testes de desempenho".

Opção 2: Memcached com Django

Comece adicionando pymemcache ao arquivo requirements.txt :

pymemcache==3.5.0

Instale as dependências:

(venv)$ pip install -r requirements.txt

Em seguida, precisamos atualizar as configurações em core/settings.py para habilitar o back-end do Memcached:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
        'LOCATION': '127.0.0.1:11211',
    }
}

Aqui, adicionamos o backend PyMemcacheCache e indicamos que o Memcached deve ser executado em nossa máquina local na porta 11211 do host local (127.0.0.1), que é a porta padrão para o Memcached.

Em seguida, precisamos instalar e executar o daemon Memcached. A maneira mais fácil de instalá-lo é através de um gerenciador de pacotes como APT, YUM, Homebrew ou Chocolatey, dependendo do seu sistema operacional:

# linux
$ apt-get install memcached
$ yum install memcached

# mac
$ brew install memcached

# windows
$ choco install memcached

Em seguida, execute-o em um terminal diferente na porta 11211:

$ memcached -p 11211

# test: telnet localhost 11211

Para obter mais informações sobre instalação e configuração do Memcached, consulte o wiki oficial .

Navegue até http://127.0.0.1:8000 em nosso navegador novamente. A primeira solicitação ainda levará os dois segundos completos, mas todas as solicitações subsequentes aproveitarão o cache. Portanto, se você atualizar ou pressionar o botão "Obter novos dados", a página deve carregar quase instantaneamente.

Como é o tempo de execução no seu terminal?

Total time: 0.03s

Testes de performance

Se observarmos o tempo que leva para carregar a primeira solicitação versus a segunda solicitação (em cache) na barra de ferramentas de depuração do Django, ela será semelhante a:

tempo de carregamento sem cache

tempo de carregamento com cache

Também na barra de ferramentas de depuração, você pode ver as operações de cache:

operações de cache

Gire o Gunicorn novamente e execute novamente os testes de desempenho:

$ ab -n 100 -c 10 http://127.0.0.1:8000/

Quais são as novas solicitações por segundo? É cerca de 36 na minha máquina!

Conclusão

Neste artigo, examinamos as diferentes opções internas para armazenamento em cache no Django, bem como os diferentes níveis de armazenamento em cache disponíveis. Também detalhamos como armazenar em cache uma visualização usando o cache Per-view do Django com Memcached e Redis.

Você pode encontrar o código final para ambas as opções, Memcached e Redis, no repositório cache-django-view .

--

Em geral, você deve procurar o cache quando a renderização da página estiver lenta devido à latência da rede de consultas ao banco de dados ou chamadas HTTP.

A partir daí, é altamente recomendável usar um backend de cache Django personalizado com Redis com um Per-viewtipo. Se você precisar de mais granularidade e controle, porque nem todos os dados no modelo são os mesmos para todos os usuários ou partes dos dados mudam com frequência, vá para o Cache de fragmento de modelo ou API de cache de baixo nível.

Fonte:  https://testdrive.io

#django #caching 

Aprenda Sobre Cache No Django