Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Caching: add global cache based on Redis (experimental) #1201

Merged
18 commits merged into from Nov 30, 2023
Merged

Caching: add global cache based on Redis (experimental) #1201

18 commits merged into from Nov 30, 2023

Conversation

ghost
Copy link

@ghost ghost commented Feb 24, 2023

Purpose

Improve our cache implementation to make room to integrate other caching solutions. Provide an optional and experimental global cache layer based on Redis. This can be handy when multiple Zonemaster instances run concurrently (when dealing with batches for instance).

Context

https://gitlab.rd.nic.fr/zonemaster/zonemaster-engine/-/commit/b96181e460b5824dcd4ac913a3210c458970126a
and
https://gitlab.rd.nic.fr/zonemaster/zonemaster-engine/-/commit/0073398f8a4c47bb6f7f7d8dae8f8a2ddd2694a6

Credits to @blacksponge for the initial proposal

Changes

  • make Cache.pm an interface
  • move old Cache.pm logic into a LocalCache module
  • new RedisCache module (loaded only if configured in profile.json).

How to test this PR

  1. install Redis and the following perl modules: Redis and Data::MessagePack. For Debian, this can be done with:
    apt install redis libredis-perl libdata-messagepack-perl
    
  2. start Redis service
  3. modify the profile.json file to specify the Redis server (see share/profile_additional_properties.json) :
    ...
    "cache": {
      "redis": {
         "server": "127.0.0.1:6379",
         "expire": 60
       }
    },
    ...
    
  4. run the tests or run Zonemaster on a zone to test
  5. check that there is data stored in Redis
    redis-cli KEYS 'ns*'
    

@ghost ghost added this to the v2023.1 milestone Feb 24, 2023
@ghost ghost requested a review from hannaeko February 24, 2023 14:54
my $redis;
our $object_cache = \%Zonemaster::Engine::Nameserver::Cache::object_cache;

my $REDIS_EXPIRE = 3600; # seconds
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does that mean that TTL in the DNS record is ignored?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this implementation yes. Rethinking about it, it might indeed be a good idea to set an expiry time to min(TTL, REDIS_EXPIRE). I've improved the logic to use the resource record TTL if available.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not understand with "if available". TTL is always set in a DNS record.

It is legal to use a shorter TLL than set in DNS, but what is the reason for for having such a limit?

If there is such a limit it should be configurable (but such configuration could be added later).

@matsduf
Copy link
Contributor

matsduf commented Mar 7, 2023

@pnax, what are your plans for this?

@ghost ghost marked this pull request as ready for review March 31, 2023 13:44
@ghost ghost added the V-Minor Versioning: The change gives an update of minor in version. label Mar 31, 2023
@matsduf
Copy link
Contributor

matsduf commented Apr 5, 2023

How to test this PR

Don't you have to start the redis service too?

Copy link
Contributor

@matsduf matsduf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documentation of how to run Redis is needed too.

Comment on lines 64 to 100
sub set_key {
my ( $self, $hash, $packet ) = @_;
my $key = "ns:" . $self->address . ":" . $hash;

my $ttl = $REDIS_EXPIRE;

$self->data->{$hash} = $packet;
if ( defined $packet ) {
my $msg = $mp->pack({
data => $packet->data,
answerfrom => $packet->answerfrom,
timestamp => $packet->timestamp,
querytime => $packet->querytime,
});
if ( $packet->answer ) {
my @rr = $packet->answer;
$ttl = min( map { $_->ttl } @rr );
$ttl = $ttl < $REDIS_EXPIRE ? $ttl : $REDIS_EXPIRE;
}
$self->redis->set( $key, $msg, 'EX', $ttl );
} else {
$self->redis->set( $key, '', 'EX', $ttl );
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand the code correctly, the TTL will always be REDIS_EXPIRE if there is no response. If REDIS_EXPIRE is configuratble that seems reasonable.

If there is a response then the lowest TTL of the DNS records in the answer section, if any, will be used for TTL of that response. That seems reasonable.

If the answer section is empty it could be an NXDOMAIN or NODATA response, and then it would be reasonable to respect the TTL of the response by using the TTL of the SOA record in the authority section, which is the way of signaling the TTL for such responses.

If the answer section is empty and the response is a referral then the TTL of the NS RRset in the authority section would be reasonable to use.

If the RCODE is neither NOERROR nor NXDOMAIN then using REDIS_EXPIRE could be reasonable.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated the logic to take your suggestion in consideration.

Comment on lines 6 to 11
"redis": {
"server": "127.0.0.1:6379"
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like such configuration should rather be in the application (Backend, CLI) rather than in the library (Engine). I guess the model requires this to be in Engine. A positive side is that the global cache can be used for multiple executions of CLI.

@ghost ghost changed the title Caching: add global cache based on Redis Caching: add global cache based on Redis (experimental) Apr 5, 2023
@ghost
Copy link
Author

ghost commented Apr 5, 2023

Todo:

  • mark this caching layer as experimental
  • improve TTL usage

Comment on lines 85 to 93
if ( $rcode eq 'NXDOMAIN' ) {
$ttl = min( map { $_->minimum } @rr );
}
elsif ( $rcode eq 'NOERROR' ) {
$ttl = min( map { $_->ttl } @rr );
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Negative caching is set to be the TTL value of SOA record (not the minimum field in the SOA RDATA) in the authority section.

If there are any NSEC or RRSIG records in the authority section, strictly they should be disregarded. Only the SOA record should be considered.

@matsduf matsduf modified the milestones: v2023.1, v2023.2 May 2, 2023
@ghost
Copy link
Author

ghost commented Sep 25, 2023

  • I've rebased this work on top of develop
  • I've addressed the remark on negative caching and using the TTL value of the SOA record instead of the minimum field

Please re-review.

@ghost ghost requested a review from matsduf September 25, 2023 13:20
Comment on lines 20 to 24
eval( <<EOS
use Data::MessagePack;
use Redis;
EOS
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you tried eval with a block instead of a string?

Suggested change
eval( <<EOS
use Data::MessagePack;
use Redis;
EOS
);
eval {
use Data::MessagePack;
use Redis;
};

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion applied.



if ( $@ ) {
die "Cant' use the Redis cache. Make sure the Data::MessagePack and Redis module are installed.\n";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
die "Cant' use the Redis cache. Make sure the Data::MessagePack and Redis module are installed.\n";
die "Can't use the Redis cache. Make sure the Data::MessagePack and Redis modules are installed.\n";

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

our $object_cache = \%Zonemaster::Engine::Nameserver::Cache::object_cache;

my $REDIS_EXPIRE = 3600; # seconds
my $REDIS_EXPIRE = 5; # seconds
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why no longer 3600 seconds? Besides, it isn’t sufficiently clear from the variable’s name that it’s merely a default value.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why no longer 3600 seconds?

I recall it being discussed during the F2F. @matsduf wanted a lower value. We agreed on 5 seconds. This is also the value suggested in the additional profile.

Copy link
Contributor

@tgreenx tgreenx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work.

I think it would be good to have documentation of the available cache engines, e.g. in lib/Zonemaster/Engine/Profile.pm, even if its in experimental state.
The unitary tests of t/profiles.t should be updated as well for the new profile key. By the way, for future-proof reasons, what do you think of making that part generic? E.g:

    "cache" : {
        "name" : "redis",
        "server": "127.0.0.1:6379",
        "expire": 5
        }

lib/Zonemaster/Engine/Nameserver/Cache/LocalCache.pm Outdated Show resolved Hide resolved
lib/Zonemaster/Engine/Nameserver/Cache/LocalCache.pm Outdated Show resolved Hide resolved
lib/Zonemaster/Engine/Nameserver/Cache/LocalCache.pm Outdated Show resolved Hide resolved
lib/Zonemaster/Engine/Nameserver/Cache/RedisCache.pm Outdated Show resolved Hide resolved
lib/Zonemaster/Engine/Nameserver/Cache/RedisCache.pm Outdated Show resolved Hide resolved
lib/Zonemaster/Engine/Nameserver/Cache/RedisCache.pm Outdated Show resolved Hide resolved
lib/Zonemaster/Engine/Nameserver/Cache.pm Outdated Show resolved Hide resolved
lib/Zonemaster/Engine/Nameserver/Cache.pm Outdated Show resolved Hide resolved
@ghost
Copy link
Author

ghost commented Nov 8, 2023

@tgreenx I should have addressed all your comments/suggestions. Good idea to have a generic entry in the profile for the cache layer.

tgreenx
tgreenx previously approved these changes Nov 8, 2023
Copy link
Contributor

@tgreenx tgreenx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tgreenx I should have addressed all your comments/suggestions. Good idea to have a generic entry in the profile for the cache layer.

Thanks. I have expanded it a bit further, see commit efb6118. Let me know what you think, otherwise LGTM.

tgreenx
tgreenx previously approved these changes Nov 8, 2023
tgreenx
tgreenx previously approved these changes Nov 8, 2023
hannaeko
hannaeko previously approved these changes Nov 9, 2023
Copy link
Member

@hannaeko hannaeko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have tested it and have no comment, it seems to be working correctly.

@hannaeko
Copy link
Member

hannaeko commented Nov 9, 2023

Can you edit the description so that the profile section uses the new cache section ?

@ghost
Copy link
Author

ghost commented Nov 9, 2023

Can you edit the description so that the profile section uses the new cache section ?

Done.

@tgreenx tgreenx dismissed stale reviews from hannaeko and themself via 08f88cd November 13, 2023 10:45
tgreenx
tgreenx previously approved these changes Nov 13, 2023
Copy link
Contributor

@tgreenx tgreenx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pnax @blacksponge please re-review. I just added a commit (08f88cd) which adds testing for the provided subkeys of profile entry cache.redis.

@matsduf
Copy link
Contributor

matsduf commented Nov 27, 2023

We need the documentation on how to use this new feature to be available. We also want the users to see that it exists. I suggest that a short section is included in the installation instructions for Zonemaster-Engine, at the end of that document as an experimental feature. As in "How to test this PR" in this PR it can be limited to one OS and very brief. The same text can probably be used.

@matsduf
Copy link
Contributor

matsduf commented Nov 27, 2023

I installed redis and the other packages on FreeBSD. Created an updated profile. Ran zonemaster-cli with the updated profile. But the redis cache is empty and rerunning the test does not speed it up. How could I check?

# redis-cli KEYS 'ns*'
(empty array)

# redis-cli KEYS '*'
(empty array)

After discussion it appears 5 seconds is to low when testing this
feature.
@ghost ghost merged commit c69fc49 into zonemaster:develop Nov 30, 2023
3 checks passed
@ghost ghost deleted the caching branch November 30, 2023 11:11
@matsduf
Copy link
Contributor

matsduf commented Dec 1, 2023

I installed redis and the other packages on FreeBSD. Created an updated profile. Ran zonemaster-cli with the updated profile. But the redis cache is empty and rerunning the test does not speed it up. How could I check?

# redis-cli KEYS 'ns*'
(empty array)

# redis-cli KEYS '*'
(empty array)

With the new default, 300 instead of 5 seconds, I get the expected result. Checking "ripe.net" took 50 seconds first time and less than 10 seconds the second time. The redis-cli command above also gave a non-empty result.

@matsduf
Copy link
Contributor

matsduf commented Mar 17, 2024

Release testing: A bug was found. Described in #1326 and a solution is provided in #1327. Else it looks fine.

@matsduf matsduf added the S-ReleaseTested Status: The PR has been successfully tested in release testing label Mar 17, 2024
This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-ReleaseTested Status: The PR has been successfully tested in release testing V-Minor Versioning: The change gives an update of minor in version.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants