NAME

Geo::Coder::Free::MissCache - process-level memory of lookups that found nothing

VERSION

Version 0.51

SYNOPSIS

use Geo::Coder::Free::MissCache;

my $scope  = Geo::Coder::Free::MissCache->scope_for('OpenAddresses', '/data/openaddresses');
my $digest = 'AB12CD34';

# A lookup found nothing: remember that, so the next caller does not
# have to read the same database to learn it again.
Geo::Coder::Free::MissCache->remember($scope, $digest);

# A caller about to do the work asks first.
if(Geo::Coder::Free::MissCache->missed($scope, $digest)) {
    print "already looked for $digest\n";
}

# The data behind the cache was rebuilt, so forget everything.
Geo::Coder::Free->clear_miss_cache();

DESCRIPTION

Every backend in Geo::Coder::Free has the same problem: an address that is not in its data is not found on every call, and each call pays for the whole search again. Geo::Coder::Free::MissCache is the one place where the backends record those misses, so the process remembers the absence once and every later call - through any instance of any backend - answers without touching a database.

Entries are held in memory, in the process, and are not shared between processes. They expire after an hour by default. That is long enough to spare a batch, a web request or a test run the repeated work, and short enough that a process which stays up after its data is rebuilt does not keep answering from a stale memory forever; Geo::Coder::Free-clear_miss_cache()> empties the cache at once for that case. Expired entries are discarded when they are next asked about, and a sweep runs at most once a minute as new misses are recorded, so the cache does not grow without bound.

A miss is only ever remembered, never inferred: nothing here decides that an answer will be missing, it only stores that a backend has already found that it is.

METHODS

scope_for

my $scope = Geo::Coder::Free::MissCache->scope_for('OpenAddresses', $dir);

Builds the name a backend stores its misses under. The parts are joined with a zero byte, so the first part names the backend and the rest say which data set is being read - two OpenAddresses directories hold different data, and a miss in one says nothing about the other. clear() uses the same convention: giving it a backend name clears every data set that backend opened.

API SPECIFICATION

input

backend => { type => 'string' }            # Local, MaxMind or OpenAddresses
dataset => { type => 'string', optional => 1 }  # zero or more, which data set

output

{ type => 'string' }

missed

my $seen = Geo::Coder::Free::MissCache->missed($scope, $key);

True if $key was remembered as a miss in $scope and has not expired. An expired entry is deleted as it is found. A missing entry is false and is not an error.

API SPECIFICATION

input

scope => { type => 'string' }   # from scope_for()
key   => { type => 'string' }

output

{ type => 'boolean' }

remember

Geo::Coder::Free::MissCache->remember($scope, $key);

Records that $key found nothing in $scope, for as long as "expires_after" says. Recording the same miss again refreshes it. Returns true.

API SPECIFICATION

input

scope => { type => 'string' }   # from scope_for()
key   => { type => 'string' }

output

{ type => 'boolean', value => 1 }

forget

Geo::Coder::Free::MissCache->forget($scope, $key);

Forgets one miss, leaving the rest of $scope alone. Returns true if there was an entry to forget, and false if $scope or $key held none, so forgetting the same miss twice returns false the second time. A backend calls this when a lookup that had been remembered as a miss now succeeds, so a hit is never skipped on the strength of an older miss.

API SPECIFICATION

input

scope => { type => 'string' }   # from scope_for()
key   => { type => 'string' }

output

{ type => 'boolean' }

clear

Geo::Coder::Free::MissCache->clear();
Geo::Coder::Free::MissCache->clear('OpenAddresses');

With no argument, forgets every miss in every scope. With a scope, forgets the misses in that scope and in every scope below it: clear('OpenAddresses') empties every OpenAddresses data set this process has opened. A scope that holds nothing, or names no backend, forgets nothing. Returns true.

API SPECIFICATION

input

scope => { type => 'string', optional => 1 }   # a backend name or a scope_for() string

output

{ type => 'boolean', value => 1 }

size

my $count = Geo::Coder::Free::MissCache->size();
my $count = Geo::Coder::Free::MissCache->size('Local');

How many misses are currently remembered, in all scopes or in one scope. Expired entries are discarded first, so this is the number of live entries.

API SPECIFICATION

input

scope => { type => 'string', optional => 1 }   # a backend name or a scope_for() string

output

{ type => 'integer', min => 0 }

expires_after

my $seconds = Geo::Coder::Free::MissCache->expires_after();
Geo::Coder::Free::MissCache->expires_after(600);

How long a remembered miss lasts, in seconds. Without an argument it answers with the current setting, which is one hour. Setting it to 0 makes every miss expire as soon as it is recorded, which turns the cache off. It is a setting of the process, not of an object, like the cache itself.

The lifetime is a whole number of seconds. A negative number, a fraction or anything that is not a number croaks expires_after() expects a non-negative whole number of seconds, and the setting is left as it was. Returns the setting in force after the call.

API SPECIFICATION

input

seconds => { type => 'integer', min => 0, optional => 1 }

output

{ type => 'integer', min => 0 }

AUTHOR

Nigel Horne <njh@nigelhorne.com>

SEE ALSO

Geo::Coder::Free

LICENSE AND COPYRIGHT

Copyright 2026 Nigel Horne.

The program code is released under the following licence: GPL2 for personal use on a single computer. All other users (including Commercial, Charity, Educational, and Government) must apply in writing for a licence for use from Nigel Horne at <njh at nigelhorne.com>.