From f730d8c56c2a7f40866e34181662ce056ceb342c Mon Sep 17 00:00:00 2001 From: Don Hardman Date: Tue, 31 Mar 2026 14:48:29 +0300 Subject: [PATCH] perf(manticore): cache table shard metadata with 30s TTL - Add Store cache instance for table metadata - Check cache before parsing table shards in getTableShards() - Cache shards with 30s TTL to reduce repeated lookups --- composer.lock | 108 ++++++++++++++++----------------- src/Cache/Store.php | 92 ++++++++++++++++++++++++++++ src/ManticoreSearch/Client.php | 23 ++++++- 3 files changed, 168 insertions(+), 55 deletions(-) create mode 100644 src/Cache/Store.php diff --git a/composer.lock b/composer.lock index 6676361..f912023 100644 --- a/composer.lock +++ b/composer.lock @@ -12,12 +12,12 @@ "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "5035cb24d5c8884364a40a72205ed6f4a3db56b9" + "reference": "68ff39175e8e94a4bb1d259407ce51a6a60f09e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/5035cb24d5c8884364a40a72205ed6f4a3db56b9", - "reference": "5035cb24d5c8884364a40a72205ed6f4a3db56b9", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/68ff39175e8e94a4bb1d259407ce51a6a60f09e6", + "reference": "68ff39175e8e94a4bb1d259407ce51a6a60f09e6", "shasum": "" }, "require": { @@ -77,7 +77,7 @@ "type": "github" } ], - "time": "2026-01-02T12:33:54+00:00" + "time": "2026-03-30T09:16:10+00:00" }, { "name": "composer/class-map-generator", @@ -85,12 +85,12 @@ "source": { "type": "git", "url": "https://github.com/composer/class-map-generator.git", - "reference": "7560f125c9c2912524d10434b0b8a9173a5763bf" + "reference": "6a9c2f0970022ab00dc58c07d0685dd712f2231b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/class-map-generator/zipball/7560f125c9c2912524d10434b0b8a9173a5763bf", - "reference": "7560f125c9c2912524d10434b0b8a9173a5763bf", + "url": "https://api.github.com/repos/composer/class-map-generator/zipball/6a9c2f0970022ab00dc58c07d0685dd712f2231b", + "reference": "6a9c2f0970022ab00dc58c07d0685dd712f2231b", "shasum": "" }, "require": { @@ -147,7 +147,7 @@ "type": "github" } ], - "time": "2026-03-02T15:04:31+00:00" + "time": "2026-03-30T15:36:56+00:00" }, { "name": "composer/composer", @@ -155,12 +155,12 @@ "source": { "type": "git", "url": "https://github.com/composer/composer.git", - "reference": "f8a87966f77267f041949ca9785df6992ec4a128" + "reference": "b83fd2977ffad7ec46e4ef6108e1912698d647f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/f8a87966f77267f041949ca9785df6992ec4a128", - "reference": "f8a87966f77267f041949ca9785df6992ec4a128", + "url": "https://api.github.com/repos/composer/composer/zipball/b83fd2977ffad7ec46e4ef6108e1912698d647f7", + "reference": "b83fd2977ffad7ec46e4ef6108e1912698d647f7", "shasum": "" }, "require": { @@ -214,7 +214,7 @@ ] }, "branch-alias": { - "dev-main": "2.9-dev" + "dev-main": "2.10-dev" } }, "autoload": { @@ -261,7 +261,7 @@ "type": "github" } ], - "time": "2026-03-06T11:33:52+00:00" + "time": "2026-03-30T11:18:13+00:00" }, { "name": "composer/metadata-minifier", @@ -1251,12 +1251,12 @@ "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "49257c96304c508223815ee965c251e7c79e614e" + "reference": "9f481cfb580db8bcecc9b2d4c63f3e13df022ad5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/49257c96304c508223815ee965c251e7c79e614e", - "reference": "49257c96304c508223815ee965c251e7c79e614e", + "url": "https://api.github.com/repos/symfony/console/zipball/9f481cfb580db8bcecc9b2d4c63f3e13df022ad5", + "reference": "9f481cfb580db8bcecc9b2d4c63f3e13df022ad5", "shasum": "" }, "require": { @@ -1341,7 +1341,7 @@ "type": "tidelift" } ], - "time": "2026-03-06T13:31:08+00:00" + "time": "2026-03-27T15:30:51+00:00" }, { "name": "symfony/dependency-injection", @@ -1349,12 +1349,12 @@ "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "d95712d0e9446b9f244b64811ffb6af7b7434213" + "reference": "cd7881a6dc84b780411199cd0584e1a53a3b9ba7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/d95712d0e9446b9f244b64811ffb6af7b7434213", - "reference": "d95712d0e9446b9f244b64811ffb6af7b7434213", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/cd7881a6dc84b780411199cd0584e1a53a3b9ba7", + "reference": "cd7881a6dc84b780411199cd0584e1a53a3b9ba7", "shasum": "" }, "require": { @@ -1426,7 +1426,7 @@ "type": "tidelift" } ], - "time": "2026-02-26T12:16:01+00:00" + "time": "2026-03-30T16:39:36+00:00" }, { "name": "symfony/deprecation-contracts", @@ -1506,12 +1506,12 @@ "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "887783fbf51c9a30ac67bf751a3a8829e4d7f2db" + "reference": "acc1d4369638a5cc0ef70fcca90cd9fae0f78caf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/887783fbf51c9a30ac67bf751a3a8829e4d7f2db", - "reference": "887783fbf51c9a30ac67bf751a3a8829e4d7f2db", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/acc1d4369638a5cc0ef70fcca90cd9fae0f78caf", + "reference": "acc1d4369638a5cc0ef70fcca90cd9fae0f78caf", "shasum": "" }, "require": { @@ -1570,7 +1570,7 @@ "type": "tidelift" } ], - "time": "2026-03-02T09:38:43+00:00" + "time": "2026-03-30T15:21:58+00:00" }, { "name": "symfony/finder", @@ -1578,12 +1578,12 @@ "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "e4b85e3592033f41426e61995f515cec73c9613b" + "reference": "a1a99d550e91c83be59247cca6a55d37ef905284" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/e4b85e3592033f41426e61995f515cec73c9613b", - "reference": "e4b85e3592033f41426e61995f515cec73c9613b", + "url": "https://api.github.com/repos/symfony/finder/zipball/a1a99d550e91c83be59247cca6a55d37ef905284", + "reference": "a1a99d550e91c83be59247cca6a55d37ef905284", "shasum": "" }, "require": { @@ -1639,7 +1639,7 @@ "type": "tidelift" } ], - "time": "2026-01-30T22:08:33+00:00" + "time": "2026-03-30T15:21:58+00:00" }, { "name": "symfony/polyfill-ctype", @@ -2314,12 +2314,12 @@ "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "7d571c714fc3950ad85685b9a6be6998769206d3" + "reference": "609a71fa1f4dcccc0c4aeda14bca725a0083d4c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/7d571c714fc3950ad85685b9a6be6998769206d3", - "reference": "7d571c714fc3950ad85685b9a6be6998769206d3", + "url": "https://api.github.com/repos/symfony/process/zipball/609a71fa1f4dcccc0c4aeda14bca725a0083d4c9", + "reference": "609a71fa1f4dcccc0c4aeda14bca725a0083d4c9", "shasum": "" }, "require": { @@ -2372,7 +2372,7 @@ "type": "tidelift" } ], - "time": "2026-01-29T09:42:44+00:00" + "time": "2026-03-30T15:21:58+00:00" }, { "name": "symfony/service-contracts", @@ -2380,12 +2380,12 @@ "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "639fa48ea277babeb67e1432ce60a029d795bd63" + "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/639fa48ea277babeb67e1432ce60a029d795bd63", - "reference": "639fa48ea277babeb67e1432ce60a029d795bd63", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d25d82433a80eba6aa0e6c24b61d7370d99e444a", + "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a", "shasum": "" }, "require": { @@ -2460,7 +2460,7 @@ "type": "tidelift" } ], - "time": "2026-02-13T20:42:21+00:00" + "time": "2026-03-28T09:44:51+00:00" }, { "name": "symfony/string", @@ -2468,12 +2468,12 @@ "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "9f209231affa85aa930a5e46e6eb03381424b30b" + "reference": "114ac57257d75df748eda23dd003878080b8e688" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/9f209231affa85aa930a5e46e6eb03381424b30b", - "reference": "9f209231affa85aa930a5e46e6eb03381424b30b", + "url": "https://api.github.com/repos/symfony/string/zipball/114ac57257d75df748eda23dd003878080b8e688", + "reference": "114ac57257d75df748eda23dd003878080b8e688", "shasum": "" }, "require": { @@ -2551,7 +2551,7 @@ "type": "tidelift" } ], - "time": "2026-02-09T09:33:46+00:00" + "time": "2026-03-24T13:12:05+00:00" }, { "name": "symfony/var-exporter", @@ -2559,12 +2559,12 @@ "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "03a60f169c79a28513a78c967316fbc8bf17816f" + "reference": "398907e89a2a56fe426f7955c6fa943ec0c77225" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/03a60f169c79a28513a78c967316fbc8bf17816f", - "reference": "03a60f169c79a28513a78c967316fbc8bf17816f", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/398907e89a2a56fe426f7955c6fa943ec0c77225", + "reference": "398907e89a2a56fe426f7955c6fa943ec0c77225", "shasum": "" }, "require": { @@ -2632,7 +2632,7 @@ "type": "tidelift" } ], - "time": "2025-09-11T10:15:23+00:00" + "time": "2026-03-24T13:12:05+00:00" } ], "packages-dev": [ @@ -2738,12 +2738,12 @@ "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "78f5d718c7f52afc31e59434f625b34c52875d72" + "reference": "c795a176780bcada3d008d07eef0c2b1b298665c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/78f5d718c7f52afc31e59434f625b34c52875d72", - "reference": "78f5d718c7f52afc31e59434f625b34c52875d72", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c795a176780bcada3d008d07eef0c2b1b298665c", + "reference": "c795a176780bcada3d008d07eef0c2b1b298665c", "shasum": "" }, "require": { @@ -2761,7 +2761,7 @@ "type": "library", "autoload": { "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + "Doctrine\\Instantiator\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2799,7 +2799,7 @@ "type": "tidelift" } ], - "time": "2026-01-14T07:50:32+00:00" + "time": "2026-03-20T19:06:08+00:00" }, { "name": "myclabs/deep-copy", @@ -4650,12 +4650,12 @@ "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "101786698b7686dc5c26aafe941e4791dbe0efee" + "reference": "f303f02c5b522e983b9c03b6c1401d46b1fc6a38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/101786698b7686dc5c26aafe941e4791dbe0efee", - "reference": "101786698b7686dc5c26aafe941e4791dbe0efee", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/f303f02c5b522e983b9c03b6c1401d46b1fc6a38", + "reference": "f303f02c5b522e983b9c03b6c1401d46b1fc6a38", "shasum": "" }, "require": { @@ -4721,7 +4721,7 @@ "type": "thanks_dev" } ], - "time": "2026-03-07T04:19:43+00:00" + "time": "2026-03-21T01:20:08+00:00" }, { "name": "theseer/tokenizer", diff --git a/src/Cache/Store.php b/src/Cache/Store.php new file mode 100644 index 0000000..3a38ca4 --- /dev/null +++ b/src/Cache/Store.php @@ -0,0 +1,92 @@ +defaultTtl = $defaultTtl; + $this->cache = new Table($capacity); + $this->cache->column('value', Table::TYPE_STRING, $valueSize); + $this->cache->column('expiry', Table::TYPE_INT, 8); + $this->cache->create(); + } + + /** + * @param string $key + * @param mixed $value Must be serializable + * @param int|null $ttl TTL in seconds, null = use default + */ + public function set(string $key, mixed $value, ?int $ttl = null): void { + $ttl ??= $this->defaultTtl; + $hashedKey = md5($key); + $this->cache->set( + $hashedKey, [ + 'value' => serialize($value), + 'expiry' => $ttl > 0 ? time() + $ttl : 0, + ] + ); + } + + /** + * @param string $key + * @return mixed Returns null if not found or expired + */ + public function get(string $key): mixed { + $hashedKey = md5($key); + /** @var array{value:string,expiry:int}|false $result */ + $result = $this->cache->get($hashedKey); + if ($result === false) { + return null; + } + if ($result['expiry'] > 0 && $result['expiry'] < time()) { + $this->cache->delete($hashedKey); + return null; + } + return unserialize($result['value']); + } + + /** + * @param string $key + * @return bool + */ + public function has(string $key): bool { + return $this->get($key) !== null; + } + + /** + * @param string $key + */ + public function remove(string $key): void { + $this->cache->delete(md5($key)); + } + + /** @return void */ + public function clear(): void { + $this->cache->destroy(); + $this->cache->create(); + } +} diff --git a/src/ManticoreSearch/Client.php b/src/ManticoreSearch/Client.php index 957e6c2..7148a3a 100644 --- a/src/ManticoreSearch/Client.php +++ b/src/ManticoreSearch/Client.php @@ -16,6 +16,7 @@ use Ds\Vector; use Exception; use Generator; +use Manticoresearch\Buddy\Core\Cache\Store; use Manticoresearch\Buddy\Core\Error\ManticoreSearchClientError; use Manticoresearch\Buddy\Core\Network\Struct; use Manticoresearch\Buddy\Core\Tool\Arrays; @@ -69,6 +70,9 @@ class Client { /** @var bool $forceSync */ protected bool $forceSync = false; + /** @var Store TTL cache for table metadata (shards, cluster map) */ + protected Store $metaCache; + /** * Initialize the Client that will use provided * @param ?string $url @@ -91,6 +95,7 @@ function () { ); $this->buddyVersion = Buddy::getVersion(); $this->clientMap = new Map; + $this->metaCache = new Store(30); // 30s TTL for table metadata } /** @@ -511,7 +516,23 @@ public function hasTable(string $table): bool { * @return array * @throws RuntimeException */ + /** + * Get the metadata cache instance + * @return Store + */ + public function getMetaCache(): Store { + return $this->metaCache; + } + + /** @return array */ public function getTableShards(string $table): array { + $cacheKey = "shards:{$table}"; + /** @var array|null $cached */ + $cached = $this->metaCache->get($cacheKey); + if ($cached !== null) { + return $cached; + } + [$locals, $agents] = $this->parseTableShards($table); $shards = []; @@ -533,7 +554,7 @@ public function getTableShards(string $table): array { 'url' => "$host:$port", ]; } - $map[$table] = $shards; + $this->metaCache->set($cacheKey, $shards); return $shards; }