← Tutti gli articoli

La memoria per chiave di Valkey cala del 37,5% dalla 7.2.14 alla 9.1.1

La memoria per chiave di Valkey cala del 37,5% dalla 7.2.14 alla 9.1.1

Lucas Yang di Percona ha pubblicato un benchmark che misura quanta memoria ogni release di Valkey risparmia effettivamente per le chiavi di stringa piccole, e il risultato principale è un calo del 37,5% nella memoria per chiave tra Valkey 7.2.14 e 9.1.1. Valkey è un archivio dati open source in memoria usato per caching, sessioni, applicazioni in tempo reale, e-commerce, ad tech e gaming. Lo stesso carico di lavoro che in media occupava 102,48 byte per chiave sulla 7.2.14 usa 64,02 byte per chiave sulla 9.1.1, il che significa che lo stesso hardware può memorizzare circa il 60% in più di queste chiavi piccole dopo l'aggiornamento. La riduzione è più utile per gli ambienti con molte stringhe, comuni in caching, sessioni e autenticazione, app in tempo reale, e-commerce, ad tech e martech, e gaming.

Per isolare l'effetto della versione, Yang ha testato cinque versioni compilate dai sorgenti: 7.2.14, 8.0.10, 8.1.9, 9.0.5 e 9.1.1. Ogni esecuzione ha usato jemalloc 5.3.0 come allocatore di memoria, perché allocatori diversi possono cambiare l'uso della memoria indipendentemente da Valkey stesso. Le istanze giravano in modalità standalone anziché in cluster, con maxmemory impostato a 0 e persistenza ed eviction disabilitate usando save "" e appendonly no. Il carico di lavoro è stato generato con valkey-benchmark -t set -n 1000000 -r 1000000 -d 16, che invia un milione di comandi SET con valori da 16 byte. Poiché anche lo spazio delle chiavi è di un milione, le collisioni di chiavi casuali lasciano circa il 63,2% delle operazioni come chiavi uniche, circa 632.000 chiavi, che corrisponde alla metodologia ufficiale del benchmark di Valkey. La memoria di base di un'istanza vuota è stata sottratta da ogni misurazione, così i numeri mostrano i veri byte marginali per chiave.

I risultati mostrano che il salto singolo più grande è arrivato con la versione 8.1. Sulla 7.2.14 il carico di lavoro ha usato 61,78 MB per 632.181 chiavi uniche, ovvero 102,48 byte per chiave. La versione 8.0.10 li ha ridotti a 57,00 MB e 94,49 byte per chiave, un calo del 7,8%. La versione 8.1.9 ha poi tagliato la memoria a 43,43 MB e 71,98 byte per chiave, un'ulteriore riduzione del 23,8% rispetto alla 8.0 e un risparmio cumulativo del 29,8% rispetto alla 7.2.14. La versione 9.0.5 è rimasta sostanzialmente invariata a 43,36 MB e 71,99 byte per chiave (una variazione dello 0,0% rispetto alla 8.1), mentre la 9.1.1 è scesa a 38,58 MB e 64,02 byte per chiave, un'altra riduzione dell'11,1% e il 37,5% in meno rispetto alla baseline 7.2.14. Per un deployment 7.2 da 1 TB, gli stessi dati occuperebbero circa 800 GB sulla 8.0, 640 GB sulla 8.1 e 630 GB sulla 9.1.

Il primo cambiamento strutturale è apparso in Valkey 8.0. Nella 7.2, una voce del dizionario (dictEntry) era una struttura da 24 byte che conteneva tre puntatori: uno alla chiave, uno al valore e uno alla voce successiva in una catena di collisione hash. Il puntatore alla chiave faceva riferimento a un blob SDS (simple dynamic string) allocato separatamente, quindi ogni lookup pagava un'allocazione di memoria extra e un salto di memoria extra solo per leggere la chiave. La versione 8.0 ha introdotto embeddedDictEntry, che posiziona i byte della chiave inline direttamente dopo la struct. Il nuovo layout è di 16 byte più la lunghezza della chiave, combinando chiave e voce in un'unica allocazione. L'effetto misurato è di 8 byte in meno per chiave, un salto di memoria in meno a ogni lookup, e il cambiamento è automatico con l'aggiornamento, senza bisogno di modifiche alla configurazione.

Valkey 8.1 ha portato il guadagno singolo più grande. Nella 8.0 il dizionario era una classica tabella hash a catena, dove raggiungere un valore significava seguire un percorso da bucket a voce a chiave a valore, quattro salti di memoria più altri per le collisioni. La versione 8.1 l'ha riscritta come una nuova struttura di tabella hash in cui un bucket è esattamente 64 byte, la dimensione di una linea di cache della CPU (il piccolo blocco di memoria che un processore carica in una sola operazione). Il bucket ha una regione di metadati da 8 byte che contiene un bit has-child-bucket, sette bit di riempimento e sette byte di hash secondario. Questo permette di saltare la maggior parte dei candidati non corrispondenti senza un accesso extra alla memoria. La chiave e il valore sono incorporati nello stesso serverObject, accorciando il lookup a circa due salti, da bucket a serverObject, e una collisione hash di solito rimane nella stessa linea di cache. La struttura dictEntry scompare del tutto. Il risultato è di circa 20 byte in meno per coppia chiave-valore, e 30 byte in meno per le chiavi con TTL. Questo spiega il grande calo dalla 8.0 alla 8.1.

L'ultimo guadagno misurato è arrivato nella 9.1. Nelle versioni precedenti di Valkey, le stringhe incorporate (embstr) sprecavano ancora un campo puntatore da 8 byte che faceva semplicemente riferimento al payload contiguo. Valkey 9.1 ha cambiato questo per le stringhe corte: i byte della stringa sono memorizzati direttamente inline negli stessi 8 byte normalmente usati per il puntatore. Non serve alcun puntatore e non viene fatta alcuna allocazione separata per il valore. Il risultato è una riduzione di memoria fino al 20% per le stringhe sotto i 128 byte, e le coppie chiave più valore da 16 byte del benchmark rientrano in quell'intervallo. Mettendo insieme i tre cambiamenti strutturali, si vede che l'efficienza di memoria di Valkey è venuta da ristrutturazioni mirate di basso livello piuttosto che da un'unica riscrittura radicale.

Oltre ai risparmi tra una release e l'altra, Yang ha anche confrontato due modi di modellare gli stessi dati. L'approccio A memorizza ogni attributo dell'utente come una chiave String separata, ad esempio SET user:1:name Alice, SET user:1:age 30, SET user:1:city Toronto e SET user:1:score 100. L'approccio B memorizza una chiave Hash per utente con quattro campi, come HSET user:1 name Alice age 30 city Toronto score 100. Per un milione di record, l'approccio A ha usato 220 MB mentre l'approccio B ne ha usati 100 MB, una riduzione del 54%. Il divario esiste perché ogni chiave paga un overhead fisso per chiave. Quattro chiavi separate pagano quell'overhead quattro volte, mentre una chiave Hash lo paga una sola volta. La scelta però non è gratuita. Le chiavi String supportano nativamente EXPIRE per il TTL per chiave, mentre Hash non supportava nativamente il TTL a livello di campo prima di Valkey 7.4. Hash vince sulle operazioni atomiche multi-campo: un singolo HSET o HMGET può leggere o scrivere più campi alla volta, mentre String richiede più round trip. Hash mantiene anche tutti i campi in uno slot automaticamente per la località nel cluster, mentre String richiede un hashtag manuale per collocare insieme chiavi correlate. Non esiste una risposta universalmente corretta; dipende se per il caso d'uso conta di più l'efficienza di memoria o la flessibilità per campo.

Nel complesso, i test di Percona quantificano come l'impronta di memoria di Valkey per le chiavi di stringa piccole sia scesa da 102,48 byte a 64,02 byte per chiave in cinque release, con il passo più grande nella 8.1 e un'ottimizzazione finale delle stringhe inline nella 9.1. Per i team che gestiscono Valkey su larga scala, un provider europeo gestito di cloud e infrastruttura dati come AEU Data può fornire il monitoraggio, i backup automatici e l'alta disponibilità che rendono più sicuro implementare e verificare queste riduzioni di memoria in produzione.

Disponibile anche in: EnglishDeutschShqip