Redis: Feature-Anfrage, zrevrangebyscore mit STORE-Option

Erstellt am 17. Sept. 2012  ·  51Kommentare  ·  Quelle: redis/redis

Hi,

Ich erstelle eine Webapp, die sortierte Sets intensiv nutzt. Oft muss ich Teilbereiche von Mengen schneiden, also verwende ich zrevrangebyscore und zinterstore. Sie funktionieren großartig. Da ich jedoch nur Teilbereiche von gegebenen Mengen schneiden muss und keine vollständigen Mengen, muss ich zuerst die Teilbereiche mit zrevrangebyscore abfragen und die Ergebnisse in einem temporären Schlüssel speichern. Dann kann ich zinterstore verwenden, um die temporären Schlüssel zu überschneiden. Anfangs habe ich den ersten Schritt im Speicher in der Client-Anwendung gemacht. Jetzt verwende ich dieses Lua-Skript:

local t = redis.call('zrangebyscore', KEYS[1], ARGV[1], ARGV[2], 'withscores') 
local i=1 
while(i<=#t) do 
    redis.call('zadd', KEYS[2], t[i+1], t[i]) 
    i=i+2 
end 
return #t/2 

Dann rufe ich das Skript auf: eval script 2 original_key result_key min max

Also passiert alles in redis. Es wäre jedoch großartig, eine optionale STORE-Option im zrangebyscore-Befehl zu haben, da die Ausführung dieses Skripts je nach Zset-Größe zu Tausenden von zadd-Aufrufen führen könnte. Vielleicht fehlt mir eine bessere Möglichkeit, dies zu tun, wenn dies der Fall ist, weisen Sie mich bitte auf eine bessere Lösung hin. Ich weiß zum Beispiel nicht, ob es möglich/empfohlen ist, zadd mit vielen Score+Member-Paaren innerhalb des lua-Skripts zu verwenden. Dies geschieht unter Verwendung der Variadic-Funktionalität dieses Befehls.

Danke und herzlichen Glückwunsch zum Redis :)

state-design-effort-needed state-proposed-feature

Hilfreichster Kommentar

Starkes +1 zum Vermeiden von Schreiberweiterungen für einen vorhandenen schreibgeschützten Befehl.

Was den neuen Befehl betrifft, denke ich, dass wir in Betracht ziehen sollten, die aktuelle Konvention zu ignorieren und ZRANGESTORE mit zusätzlichen Argumenten zu verwenden, um die verschiedenen Varianten zu unterstützen ( BYLEX , BYSCORE , REV ). Die Gründe dafür wären:

  1. Beschränken Sie die Anzahl neuer Befehle.
  2. Vermeiden Sie konsistente, aber lange Namen wie ZREVRANGEBYSCORESTORE .
  3. Z-Befehle sind bereits etwas inkonsistent, daher wäre dies nicht die erste Inkonsistenz.

Alle 51 Kommentare

Ich stimme für diesen Vorschlag. Die Verwendung von Zadd zum Durchqueren großer Sets ist zu langsam.

Ich interessiere mich auch für diese Funktion.

+1

+1

Arbeiten Sie an einem Projekt, das diese Funktion stark nutzen könnte.
+1

Ich habe einen Weg gefunden, die Variadic-Version von zadd zu verwenden. Der Trick besteht darin, unpack() zu verwenden.

local t = redis.call('zrangebyscore', KEYS[1], ARGV[1], ARGV[2], 'withscores') 
local i = 1
for i=1, #t-1, 2 do
    t[i],t[i+1] = t[i+1],t[i] # first we change the order of the elements in the table
end
if #t > 0 then redis.call("zadd", key, unpack(t)) end

Die Funktion unpack() fügt die gesamte Tabelle zum Stack hinzu, so dass es so ist, als würde man redis.call("zadd", key, t[1], t[2], t[3],.......) aufrufen. . Es gibt jedoch Beschränkungen in der Stackgröße, so dass Sie die Fehlermeldung "too many results to unpack" erhalten, wenn die Tabelle zu groß ist. Endlich habe ich diese Funktion, die die variadische Version des Befehls in Slices von 1000 Elementen aufruft

local function massive_redis_command(command, key, t)
    local i = 1
    local temp = {}
    while(i <= #t) do
        table.insert(temp, t[i+1])
        table.insert(temp, t[i])
        if #temp >= 1000 then
            redis.call(command, key, unpack(temp))
            temp = {}
        end
        i = i+2
    end
    if #temp > 0 then
        redis.call(command, key, unpack(temp))
    end
end

Warum 1000 Elemente in jedem Slice? Nun, ich habe festgestellt, dass im Lua-Quellcode eine DEFAULT_STACK_SIZE auf 1024 gesetzt ist http://www.lua.org/source/4.0.1/src_llimits.h.html

+2

+1

+1

+1

+1

Es scheint ein anständiges Interesse an dieser Funktion zu bestehen, also werde ich es versuchen, wenn die Redis-Entwickler der Meinung sind, dass es eine Ergänzung wert ist. Ich nehme an, es gibt Bedenken, ZRANGEBYSCORE und ZREVRANGEBYSCORE zu kompliziert zu machen, aber es könnte so gemacht werden, als ob der Anruf auf die folgenden zwei Arten gültig sein könnte:

Z[REV]RANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
Z[REV]RANGEBYSCORE key min max [STORE] key [LIMIT offset count]

Davon abgesehen würden diese Befehle in Bezug auf den Rückgabetyp variadisch, und ich habe das Gefühl, dass dies verboten ist (zumindest fällt mir kein anderer Befehl ein, der dies tut). Eine zweite Alternative wäre, dies in einem separaten Befehl zu tun (möglicherweise ZRANGEBYSCORESTORE was Sinn macht, aber wirklich lang ist :smiley:

Danke schön
Mike

+1 für das Hinzufügen von SORT -ähnlicher STORE Funktionalität für die Z[REV]RANGE*-Befehlsfamilie.

@michael-grunder ein unbekannter Antworttyp ist problematisch, daher gibt es trotz der syntaktischen Einfachheit von STORE in meinem Kopf (:)) kein Entkommen, stattdessen die Z*STORE-Äquivalente hinzuzufügen.

@gimenete danke für den Lua-Workaround. Ich habe einen ähnlichen Anwendungsfall, bei dem ich Sequenzen von ZRANGE* und ZINTERSTORE ausführen muss und die Kosten für den Roundtrip nicht akzeptabel sind.

Noch ein +1 von mir :)

Aus Neugier @antirez , haben Sie eine allgemeine Syntax zum Speichern des Abfrageergebnisses in einem Schlüssel in Betracht gezogen?

+1 und danke für die massive_redis_command Funktion

+1, @sunheehnus - super!, wir freuen uns

+1

Auf jeden Fall +1 dazu.

+1

+1

+1

+1

Hallo @sunheehnus , danke für die Bereitstellung eines Patches, der dies implementiert. Momentan überlege ich, ob es eine gute Idee ist, diese Befehle hinzuzufügen oder nicht. Kann ich ein Feedback dazu erhalten, warum Sie sich für die neuen Befehle entschieden haben, anstatt nur eine Option STORE erstellen? Vielleicht, weil andere Befehle diese Form haben (mit Ausnahme von SORT und einigen, die STORE verwenden)? Vielleicht können wir, wenn wir dies als STORE-Option implementieren, weniger Code schreiben, nur denjenigen, der den STORE optional implementiert, damit es nicht zu Duplizierungen kommt. Ein weiterer Grund, warum Sie diesen Weg eingeschlagen haben, könnte vielleicht Optimierung sein.

Hallo @antirez ,
Ich dachte immer, dass, wenn wir die Option SOTRE , wie @itamarhaber sagte, sie einen anderen Rückgabetyp (Array und Integer) haben kann, aber ich finde, dass der Befehl SORT Array zurückgeben kann oder Integer, und ein großer Teil meines Patches ist eine Kopie von ZRANGEBYSCORE . :-) Vielleicht ist die Option STORE also ein besserer Weg, um die Codeduplizierung zu vermeiden.

@gimenete
In Redis 3.2.4 ist die Lua-Version 5.1

127.0.0.1:6379> EVAL "return _VERSION" 0
"Lua 5.1"

Auspacken maximale Größe ist 7999

http://www.lua.org/source/5.1/luaconf.h.html#LUAI_MAXCSTACK

LUAI_MAXCSTACK definiert 8000

+1

Ich schlage einen neuen Befehl vor

ZSTOREBYSCORE Zieltaste min max

Diese Option wäre für Mengenoperationen nützlich. Ich möchte Schnittmengen von Mengen durchführen, aber ich möchte mit einer Teilmenge von einer beginnen, indem ich zuerst nach ihrer Punktzahl filtere und die Punktzahl der zweiten behalte. GEWICHTE wären für die letztere Operation nützlich. Ich bin dafür, nicht viele Daten zurückzugeben, wenn das Ergebnis in Redis in einem Zielschlüssel gespeichert wird.

Mein Anwendungsfall ist, dass ich Joins durchführen möchte, und sortierte Sets in Kombination mit der Fähigkeit von Redis, mit mehreren Schlüsseln zu arbeiten, ermöglichen dies. Ich möchte in der Lage sein, Schnittmengen an sortierten Sets mit der Partitur und nicht mit den Elementen selbst durchzuführen. Aber um die erste Filterung auf dem Index durchzuführen, möchte ich in der Lage sein, die Punktzahl (zstorebyscore) und dann die Schnittpunkte (zinterstore) anzugeben.

+1

+1

@gimenete danke für die Funktion massive_redis_command .

+1

+1

+1

+1

+1

+1

+1

Ich würde das gerne noch einmal durchgehen, eine schnelle Entscheidung über die API treffen und hoffentlich einen PR für redis 6.2 zusammenführen.
Wenn man sich die 2 PRs ansieht, fügt einer ein neues z[rev]rangebyscorestore (mit viel Codeduplizierung), und der andere fügt den vorhandenen Befehlen ein STORE Argument hinzu.

Das Problem beim zweiten Ansatz besteht darin, dass er den bisher schreibgeschützten Befehl in einen Schreibbefehl ändert und auch die Antwortart des Befehls von den Argumenten abhängt.
wir haben in der Tat bereits mehrere andere Befehle dieser Art (SORT und GEORADIUS), aber sie verursachen einige Schmerzen (zB wurde GEORADIUS_RO später hinzugefügt, um das zu mildern).

Ich denke, der richtige Ansatz besteht darin, einen neuen Befehl zu verwenden und sicherzustellen, dass die Implementierung den Code teilt, wie es SUNION und SUNIONSTORE tun.

@redis/core-team oder jemand anderen, bitte teilen Sie Ihr Feedback mit.

+1 für z[rev]rangebyscorestore als einzelner Befehl, um konsistent mit zunionstore zu bleiben, da es einfacher ist, zwischen Lese-Schreib- und Nur-Lese-Befehlen zu unterscheiden.

Ich stimme auch zu, dass wir die Definition des Befehls z[rev]rangebyscore nicht ändern sollten, um ihm im Unterbefehl eine Schreibvariante hinzuzufügen. Es ist besser, stattdessen einen expliziten Schreibbefehl z[rev]rangebyscorestore zu erstellen. Auf diese Weise vermeiden wir die Verwirrung, dass ein Befehl "meistens" schreibgeschützt ist, aber "manchmal" auch Schreiboperationen ausführen kann ... Ein weiteres gutes Beispiel in Redis heute, das mir gefallen hat, ist, dass der SUNION-Befehl schreibgeschützt ist, während SUNIONSTORE Befehl ist der äquivalente Schreibbefehl zum Speichern des Ergebnisses.

Starkes +1 zum Vermeiden von Schreiberweiterungen für einen vorhandenen schreibgeschützten Befehl.

Was den neuen Befehl betrifft, denke ich, dass wir in Betracht ziehen sollten, die aktuelle Konvention zu ignorieren und ZRANGESTORE mit zusätzlichen Argumenten zu verwenden, um die verschiedenen Varianten zu unterstützen ( BYLEX , BYSCORE , REV ). Die Gründe dafür wären:

  1. Beschränken Sie die Anzahl neuer Befehle.
  2. Vermeiden Sie konsistente, aber lange Namen wie ZREVRANGEBYSCORESTORE .
  3. Z-Befehle sind bereits etwas inkonsistent, daher wäre dies nicht die erste Inkonsistenz.

+1 zu ZRANGESTORE erscheint die Parametrisierung bestehender Befehle besser als die Einführung neuer. Der Vorschlag macht es sowieso eher wie ZUNIONSTORE.

Es scheint, wir haben eine Entscheidung. es erfordert ein wenig Refactoring (viele existierende Befehle wären nur Aliase für eine Funktion, die alle verarbeiten kann).
Will braucht natürlich auch Tests und Dokumentation.

@jonahharris willst du es anpacken?

Ich freue mich. Nächste Woche fange ich an, etwas zusammenzustellen.

@jonahharris irgendwelche Neuigkeiten zu dieser Aufgabe? einige Komplikationen haben oder einfach noch keine Zeit gefunden haben?

Hallo @oranagra. Danke fürs Nachfassen. Während ich einige andere Zeitverpflichtungen hatte, habe ich mir dies kursorisch angesehen und denke, dass der Ansatz machbar ist. Ich hoffe, dass ich diese Woche dazu komme.

Nachdem ich also ein paar verschiedene Wege gespielt habe, neige ich dazu, eine Sache sowohl im Set-Code als auch in meiner vorherigen PR nicht zu mögen, ist die spezielle Logik dafür, ob innerhalb der Funktionen geantwortet oder gespeichert werden soll - es werden viele Verzweigungen erstellt und ist schmerzhaft.

Da es nicht viele verschiedene Arten von Antworten gibt, habe ich alternativ eine spezialisierte Client-Wrapper-Struktur geschrieben, die in der generischen Funktion der obersten Ebene instanziiert wird, die die Callbacks entsprechend setzt - entweder auf ein einfaches Client-Passthrough oder eine interne Version, die verwendet, um das Ergebnis zu speichern. Auf diese Weise ist der Hauptcode von zset viel sauberer. Ebenso habe ich aus Performance-Sicht keine wirkliche Verschlechterung gesehen. Ich werde meinen Arbeitscode bald veröffentlichen, habe mich aber gefragt, was Ihre Meinung zu diesem Ansatz ist. Ähnlich würde es auch für den inter/unionstore funktionieren.

@jonahharris Sie meinen, dass die generische Befehlsimplementierung ein Objekt mit einer Reihe von Rückrufen erhält, das in einem Fall als Hinzufügen von Antworten zum Client implementiert wird, und im anderen Fall als etwas implementiert wird, das ein zset-Objekt erstellt, das schließlich hinzugefügt wird die db?

Solange es keinen Performance-Overhead durch das zweimalige Kopieren der Daten gibt, kann dies meiner Meinung nach ein guter Ansatz sein, dh verwendet Funktionszeiger anstelle von Verzweigungen und kann etwas sauberer sein (insbesondere wenn die Implementierung komplex ist und das "Antworten oder Anhängen an" Objekt"-Code wird viele Male wiederholt.

Einreichen von First-Cut-PR, die Folgendes unterstützt:

ZRANGESTORE destkey ZRANGE key start stop [WITHSCORES]
ZRANGESTORE destkey ZREVRANGE key start stop [WITHSCORES]
ZRANGESTORE destkey ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
ZRANGESTORE destkey ZREVRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
ZRANGESTORE destkey ZRANGEBYLEX key max min [LIMIT offset count]
ZRANGESTORE destkey ZREVRANGEBYLEX key max min [LIMIT offset count]

Designziele

  1. Vermeiden Sie Doppelarbeit, wenn möglich.
  2. Verwenden Sie einen identischen Codepfad, unabhängig davon, ob das Ergebnis von einem Client verwendet wird oder gespeichert werden soll.

Übersicht der Änderungen
Kurz gesagt wurde ein Ergebnishandler (zrange_result_handler) eingeführt, der das Ergebnis entweder an den Client weiterleitet oder in einem Ziel-Zset speichert – dies wird durch ZRANGE_CONSUMER_TYPE_CLIENT bzw. ZRANGE_CONSUMER_TYPE_INTERNAL bestimmt. Ebenso wurden aus der Perspektive des Refactorings und der Erfüllung der Designziele alle zrange-bezogenen Argumentanalysen und die Ausführung auf oberster Ebene in zrangeGenericCommand konsolidiert.

Wichtige Änderungen

  • Exportierte Funktionen

    • Aktuelle z(rev)rangeXXXCommand-Funktionen - Jeder richtet eine Client-Version von zrange_result_handler ein, um die Daten an den Client zurückzugeben und zrangeGenericCommand aufzurufen, um den Befehl selbst zu verarbeiten.

    • Neue zrangestoreCommand-Funktion - Richtet eine interne Version von zrange_result_handler ein, um das Ergebnis in einem zset (destkey) zu speichern, anstatt es an den Client zurückzugeben, und ruft zrangeGenericCommand mit den verbleibenden Argumenten auf, als ob sie direkt ausgeführt würden.

  • Statische Funktionen

    • zrangeGenericCommand – Der Aufruf der obersten Ebene für zrange, zrevrange, zrangebyscore, zrevrangebyscore, zrangebylex und zrevrangebylex – verarbeitet das Parsen von Argumenten und den Aufruf des entsprechenden Handlers:

    • genericZrangebylexCommand - Behandelt die spezifische Befehlsausführung.

    • genericZrangebyrankCommand - Behandelt die spezifische Befehlsausführung.

    • genericZrangebyscoreCommand - Behandelt die spezifische Befehlsausführung.

Emission von ZRANGE-bezogenen Ergebnissen
Die generischen ZrangeXXXCommand-Funktionen verwenden jetzt die Funktionszeiger beginEmission, emit und finalizeEmission innerhalb von zrange_result_handler, um die Ergebnismenge zu senden, anstatt die addReply-Aufrufe direkt. Dies sind im Wesentlichen:

  • beginResultEmission

    • Wenn Ergebnis an Client - richtet addReplyDeferredLen ein

    • Wenn Ergebnis zu speichern – löscht den alten Schlüssel (falls vorhanden) und erstellt ein neues Objekt/einen neuen Schlüssel basierend auf einer vereinfachten Strategie (ziplist/skiplist), die an der aufrufenden Site bekannt ist.

  • emitResultFromXXX

    • Wenn Ergebnis an Client - ruft addReplyArrayLen/addReplyXXX/addReplyDouble so auf, wie der aktuelle Code funktioniert.

    • Wenn Ergebnis zu speichern - ruft zsetAdd . auf

  • finalizeResultEmission

    • Wenn Ergebnis an Client - vervollständigt setDeferredArrayLen

    • Wenn Ergebnis zu speichern - gibt die Anzahl der Artikel an den Client zurück und führt bei Bedarf Benachrichtigungen aus.

Verbleibende Arbeit
Ich bin mit der Benennung einiger davon noch nicht sehr zufrieden und wie Sie für t_zset.c sehen werden, muss ich sie wieder in die Redis-Formatierung einführen - ich arbeite normalerweise nur nicht mit Redis-Code in seinem natürlichen Zustand aufgrund seiner Formatierungsinkonsistenzen und ziemlich willkürlichen Deklarationen/Definitionen, die überall verstreut sind. Ich muss auch meine Testskripts in tatsächliche Tcl-Unit-Tests formalisieren.

Egal... Ich habe einen Tag damit verbracht und wollte es herausbringen, damit die Leute damit spielen und Feedback zum Ansatz bekommen.

Wichtige offene Fragen

  • Sollte zrangestore IMMER Scores behalten? Wenn jemand nur die Mitglieder ohne Punktzahlen haben möchte, bekommt er sie derzeit. Dementsprechend wurde zrangestore entwickelt, um nur das zu speichern, was der Kunde ursprünglich erhalten hätte. Das heißt, es sei denn, withscores wird übergeben (was in der bylex-Variante nicht einmal möglich ist), wird ein Score von 0.0 verwendet. Je mehr ich damit spiele, desto lieber würde ich es persönlich bevorzugen, dass die Punktzahlen immer beibehalten werden ... aber ich sehe Gründe dafür, dass es dem Benutzer egal ist, ob alle Mitglieder auf Null gesetzt sind. Aus leistungstechnischer Sicht wurde in fast allen Fällen die tatsächliche Punktzahl bereits abgeholt, auch wenn sie nicht zurückgegeben wurde. Daher ist das Speichern der Originalpartitur im Ziel-Zset nicht wirklich aufwendig. Mich interessieren die Gedanken aller.
  • Sollte das dritte Argument von zrangestore der vollständige ZRANGE-Befehl sein? Während der frühere Kommentar BYSCORE, BYLEX REV usw. besprochen hat, habe ich den vollständigen Befehlsnamen nur verwendet, weil ZRANGE/ZREVRANGE kein differenzierendes Token haben und ich keins erfinden wollte oder feststellen musste, ob es auf ZRANGE basiert Argumente zählen. Das kann ich aber machen, wenn das gewünscht ist.

Könnte eine falsche Idee sein, aber vielleicht haben Sie, anstatt eine befehlsbezogene Sache zu machen (und nicht nur auf Z-Befehle beschränkt), vielleicht einen globalen STOREBefehl, der eigentlich ein Modifizierer für den nächsten Befehl ist, der darauf folgt (wie CLIENT REPLY SKIP), und er kann die Ausgabe des Ergebnisses einfach in den angegebenen Schlüssel einfügen, anstatt es an den Client zurückzugeben (und möglicherweise kann eine generische Infrastruktur verwendet werden) um das herum gebaut, um Befehlen zu helfen)?

Einer der Anwendungsfälle, auf die ich gehofft hatte, war die Möglichkeit, Set-Operationen durchführen zu können
auf den Partituren einer Teilmenge der zset. Wenn wir die STORE-Option haben, würde ich
wollen einen Weg, der uns diese seltsame Migration von Partituren in Mitglieder von
ein Satz.

Am Mi, 30. September 2020 um 10:01 schrieb tzickel [email protected] :

Könnte eine falsche Idee sein, aber vielleicht anstatt pro Befehl Dinge zu tun (und
nicht nur auf Z-Befehle beschränkt), haben vielleicht einen globalen STORE-Befehl, der ist
eigentlich ein Modifikator für den nächsten Befehl, der darauf folgt (wie CLIENT
REPLY ist), und es kann einfach die Ausgabe des Ergebnisses in die angegebene
key, anstatt ihn an den Client zurückzugeben (und vielleicht einige generische
Infrastruktur kann um diese herum aufgebaut werden, um Befehlen dabei zu helfen) ?


Sie erhalten dies, weil Sie einen Kommentar abgegeben haben.
Antworten Sie direkt auf diese E-Mail und zeigen Sie sie auf GitHub an
https://github.com/redis/redis/issues/678#issuecomment-701261064 , oder
Abmelden
https://github.com/notifications/unsubscribe-auth/ACBDEAJPMLJYF5D5XXNG353SILXVBANCNFSM4ABH3SYQ
.

--
Integrales z-Quadrat dz
von 1 bis zur Kubikwurzel von 3
mal den Kosinus
von drei pi über 9
entspricht dem Logarithmus der Kubikwurzel von 'e'.

@tzickel Ich denke, dass ein solcher generischer Mechanismus weniger effizient ist als das, was wir tun können, wenn wir einen expliziten Befehl haben, und er wird auch einigen Herausforderungen ausgesetzt sein (einige Kombinationen können komplizierter sein, z. B. ZRANGE wird in einem Set-Typ gespeichert, und ZRANGEWITHSCORE in den Zset-Typ).

Wie auch immer, wir können sagen, dass wir bereits einen Mechanismus für so etwas haben, nämlich Lua-Skripte. IIRC ist einer der Hauptauslöser für die Anforderung eines bestimmten Befehls, dass es effizienter ist als die Verwendung von Skripten.
Mit Skripten können Sie auch kompliziertere oder spezifischere Vorgänge ausführen, z. B. die Entscheidung, die Partituren anstelle der Mitgliedsnamen zu speichern, und sogar einige explizite Bedingungen hinzufügen. Ich denke, das sollte den erwähnten Anwendungsfall @borg286 abdecken . Einen STORE-Befehl oder eine Befehlsvariante für diese vielen sehr spezifischen Anwendungsfälle auszuführen, klingt falsch. Genau deshalb haben wir Skripte.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen