From d57ca281b022bb406585679c155371f23e1593eb Mon Sep 17 00:00:00 2001 From: Tobias Bengtsson Date: Thu, 9 Nov 2023 20:29:19 +0100 Subject: [PATCH] Make APCng updateGauge with set command lock-free in critical path When setting a value on a gauge, first apcu_store is called in updateGauge and then apcu_add is called in storeMetadata. Both of these operations takes a write lock in APCu which can cause contention. - Change updateGauge to use a compare-and-swap algorithm instead of apcu_store, unless we're adding something for the first time(s) in which case we just set it with apcu_store. - Don't call updateMetadata and storeLabelKeys unless it's the first time we're setting a gauge (matches the behaviour of inc/dec). - For extra safety, make a cheap rlock-only check in storeMetadata if the key exists before calling apcu_add with a wlock. --- src/Prometheus/Storage/APCng.php | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/Prometheus/Storage/APCng.php b/src/Prometheus/Storage/APCng.php index 6628ef97..af387be7 100644 --- a/src/Prometheus/Storage/APCng.php +++ b/src/Prometheus/Storage/APCng.php @@ -169,16 +169,27 @@ public function updateSummary(array $data): void public function updateGauge(array $data): void { $valueKey = $this->valueKey($data); + $old = apcu_fetch($valueKey); if ($data['command'] === Adapter::COMMAND_SET) { - apcu_store($valueKey, $this->convertToIncrementalInteger($data['value']), 0); - $this->storeMetadata($data); - $this->storeLabelKeys($data); + $new = $this->convertToIncrementalInteger($data['value']); + if ($old === false) { + apcu_store($valueKey, $new, 0); + $this->storeMetadata($data); + $this->storeLabelKeys($data); + + return; + } + + for ($loops = 0; $loops < self::MAX_LOOPS; $loops++) { + if (apcu_cas($valueKey, $old, $new)) { + break; + } + $old = apcu_fetch($valueKey); + } return; } - $old = apcu_fetch($valueKey); - if ($old === false) { apcu_add($valueKey, 0, 0); $this->storeMetadata($data); @@ -896,6 +907,10 @@ private function decodeLabelKey(string $str): string private function storeMetadata(array $data, bool $encoded = true): void { $metaKey = $this->metaKey($data); + if (apcu_exists($metaKey)) { + return; + } + $metaData = $this->metaData($data); $toStore = $metaData;