Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 41 additions & 13 deletions doc/admin-guide/files/records.yaml.en.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2032,9 +2032,10 @@ Origin Server Connect Attempts
:overridable:

Specifies how long (in seconds) |TS| remembers that an origin server was unreachable.
During this window, if a stale cached response exists and its age is within the cached response's ``max-age`` plus
:ts:cv:`proxy.config.http.cache.max_stale_age`, |TS| serves the stale content directly
without attempting to contact the origin server.
During this window, if a stale cached response exists and its age is within the limits configured by
:ts:cv:`proxy.config.http.cache.max_stale_age` and
:ts:cv:`proxy.config.http.cache.max_stale_age_percent`, |TS| serves the stale content directly without attempting
to contact the origin server.

.. ts:cv:: CONFIG proxy.config.http.uncacheable_requests_bypass_parent INT 1
:reloadable:
Expand Down Expand Up @@ -2128,8 +2129,8 @@ Negative Response Caching
current stale content is preserved and served. Note this is considered only on a revalidation of
already cached content. A revalidation failure means a connection failure or a 50x response code.
When considering replying with a stale response in these negative revalidating circumstances,
|TS| will respect the :ts:cv:`proxy.config.http.cache.max_stale_age` configuration and will not
use a cached response older than ``max_stale_age`` seconds plus ``max-age`` of cached content.
|TS| will respect the :ts:cv:`proxy.config.http.cache.max_stale_age` and
:ts:cv:`proxy.config.http.cache.max_stale_age_percent` limits.

A value of ``0`` disables serving stale content and a value of ``1`` enables keeping and serving stale content if revalidation fails.

Expand All @@ -2155,7 +2156,8 @@ Negative Response Caching
behavior with regard to whether it considers a stale object to be fresh enough to serve out of
cache when revalidation fails. As mentioned above in
:ts:cv:`proxy.config.http.negative_revalidating_enabled`,
:ts:cv:`proxy.config.http.cache.max_stale_age` is used for that determination.
:ts:cv:`proxy.config.http.cache.max_stale_age` and
:ts:cv:`proxy.config.http.cache.max_stale_age_percent` are used for that determination.

This configuration defaults to 1,800 seconds (30 minutes).

Expand Down Expand Up @@ -2654,7 +2656,31 @@ Cache Control
:reloadable:
:overridable:

The maximum age in seconds allowed for a stale response before it cannot be cached.
The maximum number of seconds that a stale response can be served after its ``max-age`` freshness
lifetime has passed.

When :ts:cv:`proxy.config.http.cache.max_stale_age_percent` is enabled, the smaller of the
absolute and percentage limits is used.

.. ts:cv:: CONFIG proxy.config.http.cache.max_stale_age_percent INT 0
:reloadable:
:overridable:

Limits the stale response lifetime to a percentage of its ``max-age`` freshness lifetime. The
valid range is ``0`` to ``100``. A value of ``0`` disables the percentage limit. When enabled,
|TS| uses the smaller of this limit and
:ts:cv:`proxy.config.http.cache.max_stale_age`.

For example, a value of ``25`` allows a response with a one-hour freshness lifetime
(``Cache-Control: max-age=3600``) to be served stale for up to 15 minutes (25% of one hour),
provided that the absolute stale age limit is at least 15 minutes.

A value of ``0`` applies no percentage limit. For example, with the default
:ts:cv:`proxy.config.http.cache.max_stale_age` of seven days, a response with a one-hour
freshness lifetime can be served stale for up to seven days after that freshness lifetime ends.

Responses without ``max-age`` are controlled by :ts:cv:`proxy.config.http.cache.max_stale_age`
alone.

.. ts:cv:: CONFIG proxy.config.http.cache.guaranteed_min_lifetime INT 0
:reloadable:
Expand Down Expand Up @@ -3241,13 +3267,15 @@ Dynamic Content & Content Negotiation
===== ======================================================================
``0`` Default. Disable cache and go to origin server.
``1`` Return a ``502`` error on a cache miss.
``2`` Serve stale if object's age is under ``max-age`` +
:ts:cv:`proxy.config.http.cache.max_stale_age`. Otherwise, go to
origin server.
``2`` Serve stale if the object's age is within the
:ts:cv:`proxy.config.http.cache.max_stale_age` and
:ts:cv:`proxy.config.http.cache.max_stale_age_percent` limits.
Otherwise, go to the origin server.
``3`` Return a ``502`` error on a cache miss or serve stale on a cache
revalidate if object's age is under ``max-age`` +
:ts:cv:`proxy.config.http.cache.max_stale_age`. Otherwise, go to
origin server.
revalidate if the object's age is within the
:ts:cv:`proxy.config.http.cache.max_stale_age` and
:ts:cv:`proxy.config.http.cache.max_stale_age_percent` limits.
Otherwise, go to the origin server.
``4`` Return a ``502`` error on either a cache miss or on a revalidation.
``5`` Retry Cache Read on a Cache Write Lock failure. This option together
with :ts:cv:`proxy.config.cache.enable_read_while_writer` configuration
Expand Down
1 change: 1 addition & 0 deletions doc/admin-guide/plugins/lua.en.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4822,6 +4822,7 @@ Http config constants
TS_LUA_CONFIG_BODY_FACTORY_RESPONSE_SUPPRESSION_MODE
TS_LUA_CONFIG_HTTP_CACHE_POST_METHOD
TS_LUA_CONFIG_HTTP_CACHE_TARGETED_CACHE_CONTROL_HEADERS
TS_LUA_CONFIG_HTTP_CACHE_MAX_STALE_AGE_PERCENT
TS_LUA_CONFIG_LAST_ENTRY

:ref:`TOP <admin-plugins-ts-lua>`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ TSOverridableConfigKey Value Confi
:enumerator:`TS_CONFIG_BODY_FACTORY_RESPONSE_SUPPRESSION_MODE` :ts:cv:`proxy.config.body_factory.response_suppression_mode`
:enumerator:`TS_CONFIG_HTTP_CACHE_POST_METHOD` :ts:cv:`proxy.config.http.cache.post_method`
:enumerator:`TS_CONFIG_HTTP_CACHE_TARGETED_CACHE_CONTROL_HEADERS` :ts:cv:`proxy.config.http.cache.targeted_cache_control_headers`
:enumerator:`TS_CONFIG_HTTP_CACHE_MAX_STALE_AGE_PERCENT` :ts:cv:`proxy.config.http.cache.max_stale_age_percent`
======================================================================== ====================================================================

Examples
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ Enumeration Members
.. enumerator:: TS_CONFIG_HTTP_CACHE_IGNORE_QUERY
.. enumerator:: TS_CONFIG_HTTP_CACHE_POST_METHOD
.. enumerator:: TS_CONFIG_HTTP_CACHE_TARGETED_CACHE_CONTROL_HEADERS
.. enumerator:: TS_CONFIG_HTTP_CACHE_MAX_STALE_AGE_PERCENT


Description
Expand Down
1 change: 1 addition & 0 deletions include/cripts/Configs.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ class Proxy
cripts::IntConfig max_open_write_retries{"proxy.config.http.cache.max_open_write_retries"};
cripts::IntConfig max_open_write_retry_timeout{"proxy.config.http.cache.max_open_write_retry_timeout"};
cripts::IntConfig max_stale_age{"proxy.config.http.cache.max_stale_age"};
cripts::IntConfig max_stale_age_percent{"proxy.config.http.cache.max_stale_age_percent"};
cripts::IntConfig open_read_retry_time{"proxy.config.http.cache.open_read_retry_time"};
cripts::IntConfig open_write_fail_action{"proxy.config.http.cache.open_write_fail_action"};

Expand Down
1 change: 1 addition & 0 deletions include/proxy/http/HttpConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,7 @@ struct OverridableHttpConfigParams {
MgmtInt cache_guaranteed_min_lifetime = 0;
MgmtInt cache_guaranteed_max_lifetime = 31536000;
MgmtInt cache_max_stale_age = 604800;
MgmtInt cache_max_stale_age_percent = 0;

///////////////////////////////////////////////////
// connection variables. timeouts are in seconds //
Expand Down
3 changes: 2 additions & 1 deletion include/proxy/http/OverridableConfigDefs.h
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@
X(HTTP_NEGATIVE_REVALIDATING_LIST, negative_revalidating_list, "proxy.config.http.negative_revalidating_list", STRING, HttpStatusCodeList_Conv) \
X(HTTP_CACHE_POST_METHOD, cache_post_method, "proxy.config.http.cache.post_method", INT, GENERIC) \
X(HTTP_CACHE_TARGETED_CACHE_CONTROL_HEADERS, targeted_cache_control_headers, "proxy.config.http.cache.targeted_cache_control_headers", STRING, TargetedCacheControlHeaders_Conv) \
X(SSL_CLIENT_CA_CERT_PATH, ssl_client_ca_cert_path, "proxy.config.ssl.client.CA.cert.path", STRING, NONE)
X(SSL_CLIENT_CA_CERT_PATH, ssl_client_ca_cert_path, "proxy.config.ssl.client.CA.cert.path", STRING, NONE) \
X(HTTP_CACHE_MAX_STALE_AGE_PERCENT, cache_max_stale_age_percent, "proxy.config.http.cache.max_stale_age_percent", INT, GENERIC)

// clang-format on
1 change: 1 addition & 0 deletions include/ts/apidefs.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -918,6 +918,7 @@ enum TSOverridableConfigKey {
TS_CONFIG_HTTP_CACHE_POST_METHOD,
TS_CONFIG_HTTP_CACHE_TARGETED_CACHE_CONTROL_HEADERS,
TS_CONFIG_SSL_CLIENT_CA_CERT_PATH,
TS_CONFIG_HTTP_CACHE_MAX_STALE_AGE_PERCENT,
TS_CONFIG_LAST_ENTRY,
};

Expand Down
4 changes: 3 additions & 1 deletion src/proxy/http/HttpConfig.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1109,6 +1109,7 @@ HttpConfig::startup()
HttpEstablishStaticConfigLongLong(c.oride.cache_guaranteed_max_lifetime, "proxy.config.http.cache.guaranteed_max_lifetime");

HttpEstablishStaticConfigLongLong(c.oride.cache_max_stale_age, "proxy.config.http.cache.max_stale_age");
HttpEstablishStaticConfigLongLong(c.oride.cache_max_stale_age_percent, "proxy.config.http.cache.max_stale_age_percent");

HttpEstablishStaticConfigByte(c.oride.srv_enabled, "proxy.config.srv_enabled");

Expand Down Expand Up @@ -1456,7 +1457,8 @@ HttpConfig::reconfigure()
params->oride.cache_guaranteed_min_lifetime = m_master.oride.cache_guaranteed_min_lifetime;
params->oride.cache_guaranteed_max_lifetime = m_master.oride.cache_guaranteed_max_lifetime;

params->oride.cache_max_stale_age = m_master.oride.cache_max_stale_age;
params->oride.cache_max_stale_age = m_master.oride.cache_max_stale_age;
params->oride.cache_max_stale_age_percent = m_master.oride.cache_max_stale_age_percent;

params->oride.srv_enabled = m_master.oride.srv_enabled;

Expand Down
14 changes: 13 additions & 1 deletion src/proxy/http/HttpTransact.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6396,8 +6396,20 @@ HttpTransact::is_stale_cache_response_returnable(State *s)
time_t current_age = HttpTransactCache::calculate_document_age(s->cache_info.object_read->request_sent_time_get(),
s->cache_info.object_read->response_received_time_get(),
cached_response, cached_response->get_date(), s->current.now);

MgmtInt max_age = get_max_age(cached_response);
MgmtInt max_stale_age = s->txn_conf->cache_max_stale_age;
MgmtInt max_stale_percentage =
std::clamp(s->txn_conf->cache_max_stale_age_percent, static_cast<MgmtInt>(0), static_cast<MgmtInt>(100));

if (max_stale_percentage > 0 && max_age >= 0) {
MgmtInt percent_max_stale_age = max_age * max_stale_percentage / 100;

max_stale_age = std::min(max_stale_age, percent_max_stale_age);
}

// Negative age is overflow
if ((current_age < 0) || (current_age > s->txn_conf->cache_max_stale_age + get_max_age(cached_response))) {
if ((current_age < 0) || (current_age > max_age + max_stale_age)) {
TxnDbg(dbg_ctl_http_trans, "document age is too large %" PRId64, (int64_t)current_age);
return false;
}
Expand Down
6 changes: 4 additions & 2 deletions src/records/RecordsConfig.cc
Original file line number Diff line number Diff line change
Expand Up @@ -633,8 +633,8 @@ static constexpr RecordElement RecordsConfig[] =
// #
// # 0 - default. disable cache and goto origin
// # 1 - return error if cache miss
// # 2 - serve stale until proxy.config.http.cache.max_stale_age, then goto origin, if revalidate
// # 3 - return error if cache miss or serve stale until proxy.config.http.cache.max_stale_age, then goto origin, if revalidate
// # 2 - serve stale within the configured stale age limits, then goto origin, if revalidate
// # 3 - return error if cache miss or serve stale within the configured stale age limits, then goto origin, if revalidate
// # 4 - return error if cache miss or if revalidate
// # 5 - retry cache read (read-while-writer) on write lock failure, goto origin if retries exhausted
// # 6 - retry cache read on write lock failure, if retries exhausted serve stale if allowed, otherwise goto origin
Expand All @@ -660,6 +660,8 @@ static constexpr RecordElement RecordsConfig[] =
,
{RECT_CONFIG, "proxy.config.http.cache.max_stale_age", RECD_INT, "604800", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
,
{RECT_CONFIG, "proxy.config.http.cache.max_stale_age_percent", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_INT, "[0-100]", RECA_NULL}
,
{RECT_CONFIG, "proxy.config.http.cache.range.lookup", RECD_INT, "1", RECU_NULL, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
,
{RECT_CONFIG, "proxy.config.http.cache.range.write", RECD_INT, "0", RECU_NULL, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
Expand Down
3 changes: 3 additions & 0 deletions tests/gold_tests/cache/proxy_serve_stale.test.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,8 @@
# Verify that stale content is served when the parent is down.
Test.ATSReplayTest(replay_file="replay/proxy_serve_stale.replay.yaml")

# Verify that stale content respects the percentage maximum stale age.
Test.ATSReplayTest(replay_file="replay/max_stale_age_percent.replay.yaml")

# Verify that stale content is served when the origin server is down.
Test.ATSReplayTest(replay_file="replay/proxy_serve_stale_origin_down.replay.yaml")
117 changes: 117 additions & 0 deletions tests/gold_tests/cache/replay/max_stale_age_percent.replay.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

meta:
version: "1.0"

blocks:

- origin_response: &origin_response
server-response:
status: 500
reason: "Internal Server Error"
headers:
fields:
- [ Content-Length, 16 ]
- [ X-Response, should_not_see ]

autest:
description: 'Verify the percentage maximum stale age limit'

dns:
name: 'dns-max-stale-age-percent'

server:
name: 'server-max-stale-age-percent'

client:
name: 'client-max-stale-age-percent'

ats:
name: 'ts-max-stale-age-percent'
process_config:
enable_cache: true

records_config:
proxy.config.http.push_method_enabled: 1
proxy.config.http.parent_proxy.fail_threshold: 1
proxy.config.http.parent_proxy.total_connect_attempts: 1
proxy.config.http.cache.max_stale_age: 100
proxy.config.http.cache.max_stale_age_percent: 50
proxy.config.http.parent_proxy.self_detect: 0

parent_config:
- 'dest_domain=. parent="localhost:82" round_robin=consistent_hash go_direct=false'

remap_config:
- from: "http://example.com/"
to: "http://localhost:{SERVER_HTTP_PORT}/"

sessions:
- transactions:

# Populate the cache with a four-second freshness lifetime.
- client-request:
method: "PUSH"
version: "1.1"
url: /percentage
headers:
fields:
- [ Host, example.com ]
- [ uuid, push ]
- [ Content-Length, 73 ]
content:
encoding: plain
data: "HTTP/1.1 200 OK\nContent-Length: 6\nCache-Control: public,max-age=4\n\nCACHED"

<<: *origin_response

proxy-response:
status: 201

# At five seconds old, the object is stale but remains within the two-second
# percentage stale window.
- client-request:
delay: 5s
method: "GET"
version: "1.1"
url: /percentage
headers:
fields:
- [ Host, example.com ]
- [ uuid, within_percentage_limit ]

<<: *origin_response

proxy-response:
status: 200

# At eight seconds old, the object exceeds max-age (4 seconds) plus 50%
# (2 seconds), even though the absolute stale age limit is 100 seconds.
- client-request:
delay: 3s
method: "GET"
version: "1.1"
url: /percentage
headers:
fields:
- [ Host, example.com ]
- [ uuid, past_percentage_limit ]

<<: *origin_response

proxy-response:
status: 502
1 change: 1 addition & 0 deletions tests/gold_tests/records/gold/full_records.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ records:
max_open_read_retries: -1
max_open_write_retries: 1
max_stale_age: 604800
max_stale_age_percent: 0
open_read_retry_time: 10
open_write_fail_action: 0
post_method: 0
Expand Down
1 change: 1 addition & 0 deletions tests/gold_tests/records/legacy_config/full_records.config
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ CONFIG proxy.config.http.cache.open_write_fail_action INT 0
CONFIG proxy.config.http.cache.when_to_revalidate INT 0
CONFIG proxy.config.http.cache.required_headers INT 2
CONFIG proxy.config.http.cache.max_stale_age INT 604800
CONFIG proxy.config.http.cache.max_stale_age_percent INT 0
CONFIG proxy.config.http.cache.range.lookup INT 1
CONFIG proxy.config.http.cache.range.write INT 0
CONFIG proxy.config.http.cache.heuristic_min_lifetime INT 3600
Expand Down