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

Support for QueryResult caching #1251

Merged
merged 1 commit into from Nov 5, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
81 changes: 70 additions & 11 deletions DefaultSettings.php
Expand Up @@ -1030,18 +1030,20 @@
$GLOBALS['smwgEnabledFulltextSearch'] = false;

##
# Throttle the amount of expected index updates.
# Throttle index updates
#
# It can be of advantage to postpone the update using a deferred job execution to
# decouple changes to the storage back-end and the fulltext index table.
# The objective is to postpone an update by relying on a deferred process that
# runs the index update decoupled from the storage back-end update.
#
# In case `smwgFulltextDeferredUpdate` and `$GLOBALS['smwgEnabledDeferredUpdate']` are
# enabled then the updater will try to open a new process for posting instructions
# to execute the `SearchTableUpdateJob` immediately otherwise `SearchTableUpdateJob`
# will be enqueued and `runJobs.php` is required to be schedule for execution.
# both enabled then the updater will try to open a new request and posting instructions
# to execute the `SearchTableUpdateJob` immediately in background. If the request
# cannot be executed then the `SearchTableUpdateJob` will be enqueued and requires
# `runJobs.php` to schedule the index table update.
#
# If a user wants to avoid the JobQueue for executing updates via `SearchTableUpdateJob`
# then this setting should be disabled.
# If a user wants to push updates to the updater immediately then this setting needs
# to be disabled but by disabling this setting update lag may increase due to having
# the process being executed synchronously to the wikipage, store-backend storage.
#
# @since 2.5
# @default true
Expand All @@ -1051,10 +1053,10 @@
##
# Fulltext search table options
#
# This setting directly influences how a ft table is created therefore please
# change the content with caution.
# This setting directly influences how a ft table is created therefore change
# the content with caution.
#
# - MySQL version 5.6 or later with only MyISAM and InnoDB storage engines
# - MySQL version 5.5 or later with only MyISAM and InnoDB storage engines
# to support full-text search (according to sources)
#
# - MariaDB full-text indexes can be used only with MyISAM and Aria tables,
Expand Down Expand Up @@ -1138,3 +1140,60 @@
# @default false
##
$GLOBALS['smwgQTemporaryTablesAutoCommitMode'] = false;

###
# Support to store a computed subject list that were fetched from the QueryEngine
# (not the string result generated from a result printer) and improve general
# page-loading time for articles that contain embedded queries and decrease
# server load on query requests.
#
# It is recommended that `smwgEnabledQueryDependencyLinksStore` is enabled to make
# use of automatic query results cache eviction.
#
# @since 2.5 (experimental)
#
# @default: CACHE_NONE (== that this feature is disabled)
##
$GLOBALS['smwgQueryResultCacheType'] = CACHE_NONE;
##

###
# Specifies the lifetime of embedded query and their results fetched from a
# QueryEngine for when `smwgQueryResultCacheType` is enabled.
#
# @since 2.5
##
$GLOBALS['smwgQueryResultCacheLifetime'] = 60 * 60 * 24; // a day
##

###
# Specifies the lifetime of non-embedded queries (Special:Ask, API etc.) and their
# results that are fetched from a QueryEngine for when `smwgQueryResultCacheType` is
# enabled.
#
# This setting can also be used to minimize a possible DoS vector by preventing
# an advisory to make unlimited query requests from either Special:Ask or the
# API that may lock the DB due to complex query answering and instead being
# rerouted to the cache once a result has been computed.
#
# @note Non-embedded queries cannot not be tracked using the `QueryDependencyLinksStore`
# (subject is being missing that would identify the entity) therefore
# an auto-purge mechanism as in case of an embedded entity is not possible hence
# the lifetime should be carefully selected to provide the necessary means for a
# user and the application.
#
# 0/false as setting to disable caching of non-embedded queries.
#
# @since 2.5
##
$GLOBALS['smwgQueryResultNonEmbeddedCacheLifetime'] = 60 * 10; // 10 min
##

###
# Enables the manual refresh for embedded queries when the action=purge event is
# triggered.
#
# @since 2.5
##
$GLOBALS['smwgQueryResultCacheRefreshOnPurge'] = true;
##
2 changes: 1 addition & 1 deletion includes/ParserData.php
Expand Up @@ -296,7 +296,7 @@ public function updateStore( $enabledDeferredUpdate = false ) {
$storeUpdater->doUpdate();
} );

$deferredCallableUpdate->setOrigin( __METHOD__ );
$deferredCallableUpdate->setOrigin( __METHOD__ . ' :: ' . $this->semanticData->getSubject()->getHash() );

$deferredCallableUpdate->enabledDeferredUpdate(
$enabledDeferredUpdate
Expand Down
4 changes: 4 additions & 0 deletions includes/Settings.php
Expand Up @@ -148,6 +148,10 @@ public static function newFromGlobals() {
'smwgFulltextSearchMinTokenSize' => $GLOBALS['smwgFulltextSearchMinTokenSize'],
'smwgFulltextLanguageDetection' => $GLOBALS['smwgFulltextLanguageDetection'],
'smwgQTemporaryTablesAutoCommitMode' => $GLOBALS['smwgQTemporaryTablesAutoCommitMode'],
'smwgQueryResultCacheType' => $GLOBALS['smwgQueryResultCacheType'],
'smwgQueryResultCacheLifetime' => $GLOBALS['smwgQueryResultCacheLifetime'],
'smwgQueryResultNonEmbeddedCacheLifetime' => $GLOBALS['smwgQueryResultNonEmbeddedCacheLifetime'],
'smwgQueryResultCacheRefreshOnPurge' => $GLOBALS['smwgQueryResultCacheRefreshOnPurge'],
);

$settings = $settings + array(
Expand Down
40 changes: 37 additions & 3 deletions includes/query/SMW_Query.php
Expand Up @@ -35,6 +35,8 @@
*/
class SMWQuery implements QueryContext {

const ID_PREFIX = '_QUERY';

/**
* The time the QueryEngine required to answer a query condition
*/
Expand Down Expand Up @@ -396,7 +398,7 @@ public function toArray() {
// @2.4 Keep the queryID stable with previous versions unless
// a query source is selected. The "same" query executed on different
// remote systems requires a different queryID
if ( $this->querySource !== '' ) {
if ( $this->querySource !== null && $this->querySource !== '' ) {
$serialized['parameters']['source'] = $this->querySource;
}

Expand All @@ -411,12 +413,44 @@ public function toArray() {
}

/**
* @note Before 2.5, toArray was used to generate the content, as of 2.5
* only parameters that influence the result of an query is included.
*
* @since 2.1
*
* @return string
*/
public function getHash() {
return HashBuilder::createHashIdForContent( $this->toArray() );

// FIXME 3.0 Leave the hash unchanged to avoid unnecessary BC issues in
// case the cache is not used.
if ( $GLOBALS['smwgQueryResultCacheType'] === false ||
$GLOBALS['smwgQueryResultCacheType'] === CACHE_NONE ||
$GLOBALS['smwgQueryResultCacheType'] === 'hash' ) {
return HashBuilder::createFromArray( $this->toArray() );
}

// For an optimal (less fragmentation) use of the cache, only use
// elements that directly influence the result list
$serialized = array();

$serialized['conditions'] = $this->getQueryString();
$serialized['parameters'] = array(
'limit' => $this->limit,
'offset' => $this->offset,
'sortkeys' => $this->sortkeys,
'querymode' => $this->querymode
);

if ( $this->querySource !== null && $this->querySource !== '' ) {
$serialized['parameters']['source'] = $this->querySource;
}

// printouts are avoided as part of the hash as they not influence the
// match process and are only resolved after the query result has been
// retrieved

return HashBuilder::createFromArray( $serialized );
}

/**
Expand All @@ -434,7 +468,7 @@ public function getAsString() {
* @return string
*/
public function getQueryId() {
return '_QUERY' . $this->getHash();
return self::ID_PREFIX . $this->getHash();
}

}
2 changes: 1 addition & 1 deletion includes/storage/SQLStore/SMW_SQLStore3.php
Expand Up @@ -313,7 +313,7 @@ public function getQueryResult( SMWQuery $query ) {
$result = null;
$start = microtime( true );

if ( \Hooks::run( 'SMW::Store::BeforeQueryResultLookupComplete', array( $this, $query, &$result ) ) ) {
if ( \Hooks::run( 'SMW::Store::BeforeQueryResultLookupComplete', array( $this, $query, &$result, $this->factory->newSlaveQueryEngine() ) ) ) {
$result = $this->fetchQueryResult( $query );
}

Expand Down
1 change: 1 addition & 0 deletions phpunit.xml.dist
Expand Up @@ -67,6 +67,7 @@
<var name="smwgEnabledFulltextSearch" value="false"/>
<var name="smwgEnabledHttpDeferredJobRequest" value="false"/>
<var name="smwgEnabledQueryDependencyLinksStore" value="true"/>
<var name="smwgQueryResultCacheType" value="hash"/>
<var name="benchmarkQueryRepetitionExecutionThreshold" value="5"/>
<var name="benchmarkQueryLimit" value="500"/>
<var name="benchmarkQueryOffset" value="0"/>
Expand Down
27 changes: 27 additions & 0 deletions src/ApplicationFactory.php
Expand Up @@ -165,6 +165,15 @@ public function newCacheFactory() {
return $this->callbackLoader->load( 'CacheFactory', $this->getSettings()->get( 'smwgCacheType' ) );
}

/**
* @since 2.2
*
* @return CacheFactory
*/
public function getCacheFactory() {
return $this->callbackLoader->singleton( 'CacheFactory', $this->getSettings()->get( 'smwgCacheType' ) );
}

/**
* @since 2.5
*
Expand Down Expand Up @@ -344,6 +353,15 @@ public function getCachedPropertyValuesPrefetcher() {
return $this->callbackLoader->singleton( 'CachedPropertyValuesPrefetcher' );
}

/**
* @since 2.5
*
* @return CachedQueryResultPrefetcher
*/
public function getCachedQueryResultPrefetcher() {
return $this->callbackLoader->singleton( 'CachedQueryResultPrefetcher' );
}

/**
* @since 2.4
*
Expand Down Expand Up @@ -431,6 +449,15 @@ public function getQueryFactory() {
return $this->callbackLoader->singleton( 'QueryFactory' );
}

/**
* @since 2.5
*
* @return LoggerInterface
*/
public function getMediaWikiLogger() {
return $this->callbackLoader->singleton( 'MediaWikiLogger' );
}

private static function registerBuilder( CallbackLoader $callbackLoader = null ) {

if ( $callbackLoader === null ) {
Expand Down
10 changes: 1 addition & 9 deletions src/CacheFactory.php
Expand Up @@ -146,15 +146,7 @@ public function newMediaWikiCompositeCache( $mediaWikiCacheType = null ) {
* @return BlobStore
*/
public function newBlobStore( $namespace, $cacheType = null, $cacheLifetime = 0 ) {

$blobStore = $this->callbackInstantiator->load( 'BlobStore', $namespace, $cacheType, $cacheLifetime );

// If CACHE_NONE is selected, disable the usage
$blobStore->setUsageState(
$cacheType !== CACHE_NONE
);

return $blobStore;
return $this->callbackInstantiator->load( 'BlobStore', $namespace, $cacheType, $cacheLifetime );
}

}
5 changes: 5 additions & 0 deletions src/CachedPropertyValuesPrefetcher.php
Expand Up @@ -30,6 +30,11 @@ class CachedPropertyValuesPrefetcher {
*/
const VERSION = '0.4.1';

/**
* Namespace occupied by the BlobStore
*/
const CACHE_NAMESPACE = 'smw:pvp:store';

/**
* @var Store
*/
Expand Down