From dc729fd8adc97f7950dbc7991cad82c98ccd2621 Mon Sep 17 00:00:00 2001 From: Amaury Decreme Date: Tue, 7 Feb 2017 11:34:20 +0100 Subject: [PATCH 1/7] Support for REDIS priority queues This commit adds support for ZSET queues in REDIS with data_type 'list'. Batch is supported. Score of the message is based on output (see logstash-output-redis). The commit adds the following options to the configuration: * **priority**: *boolean*, default false, enable priority mode * **priority_reverse *boolean*, default false, pop high score items first Usage example: input { redis { host => "127.0.0.1" port => 6379 data_type => "list" key => "syslog" priority => "syslog" priority_reverse => true } } --- lib/logstash/inputs/redis.rb | 87 ++++++++++++++++++++++++++++++------ 1 file changed, 73 insertions(+), 14 deletions(-) diff --git a/lib/logstash/inputs/redis.rb b/lib/logstash/inputs/redis.rb index 203f854..1e5a6a1 100644 --- a/lib/logstash/inputs/redis.rb +++ b/lib/logstash/inputs/redis.rb @@ -4,13 +4,18 @@ require "logstash/inputs/threadable" require 'redis' -# This input will read events from a Redis instance; it supports both Redis channels and lists. +# This input will read events from a Redis instance; it supports both Redis channels and lists +# with or without priority mode. +# # The list command (BLPOP) used by Logstash is supported in Redis v1.3.1+, and # the channel commands used by Logstash are found in Redis v1.3.8+. # While you may be able to make these Redis versions work, the best performance # and stability will be found in more recent stable versions. Versions 2.6.0+ # are recommended. # +# The priority 'list' commands (ZREMRANGEBYRANK, ZRANGE, ZREVRANGE) used by Logstash are supported +# in Redis v2.0.0+ +# # For more information about Redis, see # # `batch_count` note: If you use the `batch_count` setting, you *must* use a Redis version 2.6.0 or @@ -41,8 +46,14 @@ module LogStash module Inputs class Redis < LogStash::Inputs::Threadable # The name of a Redis list or channel. config :key, :validate => :string, :required => true - # Specify either list or channel. If `redis\_type` is `list`, then we will BLPOP the - # key. If `redis\_type` is `channel`, then we will SUBSCRIBE to the key. + # Enable priority mode + config :priority, :validate => :boolean, :default => false + + # Pop high scores item first + config :priority_reverse, :validate => :boolean, :default => false + + # Specify either list or channel. If `redis\_type` is `list`, then we will BLPOP/ZRANGE/ZREVRANGE + # the key. If `redis\_type` is `channel`, then we will SUBSCRIBE to the key. # If `redis\_type` is `pattern_channel`, then we will PSUBSCRIBE to the key. config :data_type, :validate => [ "list", "channel", "pattern_channel" ], :required => true @@ -138,12 +149,36 @@ def connect # private def load_batch_script(redis) #A Redis Lua EVAL script to fetch a count of keys - redis_script = < 1) - return unless item # from timeout or other conditions + if @priority then + redis.watch(@key) do + if @priority_reverse then + item = redis.zrevrange(@key, 0, 0, :timeout => 1) + else + item = redis.zrange(@key, 0, 0, :timeout => 1) + end + + if item.size.zero? + sleep BATCH_EMPTY_SLEEP + end + + return unless item.size > 0 + + redis.multi do |multi| + redis.zrem(@key, item) + end + + queue_event(item.first, output_queue) + end + else + item = redis.blpop(@key, 0, :timeout => 1) + return unless item # from timeout or other conditions + + # blpop returns the 'key' read from as well as the item result + # we only care about the result (2nd item in the list). + queue_event(item.last, output_queue) + end - # blpop returns the 'key' read from as well as the item result - # we only care about the result (2nd item in the list). - queue_event(item.last, output_queue) end # private From 5d974d5fbf00a6b94eed2b429b5b225662120842 Mon Sep 17 00:00:00 2001 From: Amaury Decreme Date: Thu, 8 Jun 2017 16:19:38 +0200 Subject: [PATCH 2/7] Data_type sortedset instead of priority option Use the data_type sortedset instead of the priority option --- lib/logstash/inputs/redis.rb | 170 +++++++++++++++++++++++------------ 1 file changed, 114 insertions(+), 56 deletions(-) diff --git a/lib/logstash/inputs/redis.rb b/lib/logstash/inputs/redis.rb index 1e5a6a1..d96fe22 100644 --- a/lib/logstash/inputs/redis.rb +++ b/lib/logstash/inputs/redis.rb @@ -46,16 +46,14 @@ module LogStash module Inputs class Redis < LogStash::Inputs::Threadable # The name of a Redis list or channel. config :key, :validate => :string, :required => true - # Enable priority mode - config :priority, :validate => :boolean, :default => false - - # Pop high scores item first + # Pop high scores item first in sortedset. No effect for data_type config :priority_reverse, :validate => :boolean, :default => false - # Specify either list or channel. If `redis\_type` is `list`, then we will BLPOP/ZRANGE/ZREVRANGE + # Specify either list or channel. If `redis\_type` is `list`, then we will BLPOP # the key. If `redis\_type` is `channel`, then we will SUBSCRIBE to the key. - # If `redis\_type` is `pattern_channel`, then we will PSUBSCRIBE to the key. - config :data_type, :validate => [ "list", "channel", "pattern_channel" ], :required => true + # If `redis\_type` is `pattern_channel`, then we will PSUBSCRIBE to the key. + # If `redis\_type` is `sortedset`, then we will ZRANGE/ZREVRANGE + config :data_type, :validate => [ "list", "channel", "pattern_channel", "sortedset" ], :required => true # The number of events to return from Redis using EVAL. config :batch_count, :validate => :number, :default => 125 @@ -87,6 +85,9 @@ def register if @data_type == 'list' || @data_type == 'dummy' @run_method = method(:list_runner) @stop_method = method(:list_stop) + elsif @data_type == 'sortedset' + @run_method = method(:sortedset_runner) + @stop_method = method(:sortedset_stop) elsif @data_type == 'channel' @run_method = method(:channel_runner) @stop_method = method(:subscribe_stop) @@ -95,7 +96,9 @@ def register @stop_method = method(:subscribe_stop) end + #TODO voir à terme comment fusionner ces deux méthodes @list_method = batched? ? method(:list_batch_listener) : method(:list_single_listener) + @sortedset_method = batched? ? method(:sortedset_batch_listener) : method(:sortedset_single_listener) @identity = "#{@redis_url} #{@data_type}:#{@key}" @logger.info("Registering Redis", :identity => @identity) @@ -123,6 +126,11 @@ def is_list_type? @data_type == 'list' end + # private + def is_sortedset_type? + @data_type == 'sortedset' + end + # private def redis_params { @@ -142,41 +150,45 @@ def internal_redis_builder # private def connect redis = new_redis_instance - load_batch_script(redis) if batched? && is_list_type? + list_load_batch_script(redis) if batched? && is_list_type? + sortedset_load_batch_script(redis) if batched? && is_sortedset_type? redis end # def connect # private - def load_batch_script(redis) + def list_load_batch_script(redis) #A Redis Lua EVAL script to fetch a count of keys redis_script = "local batchsize = tonumber(ARGV[1])\n" - if @priority then - redis_script << "local zcard = tonumber(redis.call('zcard', KEYS[1]))\n" - end redis_script << "local result = redis.call('" - if @priority then - if @priority_reverse then - redis_script << 'zrevrange' - else - redis_script << 'zrange' - end + redis_script << 'lrange' + redis_script << "', KEYS[1], 0, batchsize)\n" + redis_script << "redis.call('" + redis_script << "ltrim', KEYS[1], batchsize + 1, -1" + redis_script << ")\nreturn result\n" + + @redis_script_sha = redis.script(:load, redis_script) + end + + # private + def sortedset_load_batch_script(redis) + #A Redis Lua EVAL script to fetch a count of keys + redis_script = "local batchsize = tonumber(ARGV[1])\n" + redis_script << "local zcard = tonumber(redis.call('zcard', KEYS[1]))\n" + redis_script << "local result = redis.call('" + if @priority_reverse then + redis_script << 'zrevrange' else - redis_script << 'lrange' + redis_script << 'zrange' end redis_script << "', KEYS[1], 0, batchsize)\n" redis_script << "redis.call('" - if @priority then - redis_script << "zremrangebyrank', KEYS[1], " - if @priority_reverse then - redis_script << "zcard - batchsize - 1, zcard" - else - redis_script << "0, batchsize" - end + redis_script << "zremrangebyrank', KEYS[1], " + if @priority_reverse then + redis_script << "zcard - batchsize - 1, zcard" else - redis_script << "ltrim', KEYS[1], batchsize + 1, -1" - end - + redis_script << "0, batchsize" + end redis_script << ")\nreturn result\n" @redis_script_sha = redis.script(:load, redis_script) @@ -250,7 +262,7 @@ def list_batch_listener(redis, output_queue) rescue ::Redis::CommandError => e if e.to_s =~ /NOSCRIPT/ then @logger.warn("Redis may have been restarted, reloading Redis batch EVAL script", :exception => e); - load_batch_script(redis) + list_load_batch_script(redis) retry else raise e @@ -259,37 +271,83 @@ def list_batch_listener(redis, output_queue) end def list_single_listener(redis, output_queue) - if @priority then - redis.watch(@key) do - if @priority_reverse then - item = redis.zrevrange(@key, 0, 0, :timeout => 1) - else - item = redis.zrange(@key, 0, 0, :timeout => 1) - end - - if item.size.zero? - sleep BATCH_EMPTY_SLEEP - end - - return unless item.size > 0 - - redis.multi do |multi| - redis.zrem(@key, item) - end - - queue_event(item.first, output_queue) + item = redis.blpop(@key, 0, :timeout => 1) + return unless item # from timeout or other conditions + + # blpop returns the 'key' read from as well as the item result + # we only care about the result (2nd item in the list). + queue_event(item.last, output_queue) + end + + # private + def sortedset_stop + return if @redis.nil? || !@redis.connected? + + @redis.quit rescue nil + @redis = nil + end + + # private + def sortedset_runner(output_queue) + while !stop? + begin + @redis ||= connect + @sortedset_method.call(@redis, output_queue) + rescue ::Redis::BaseError => e + @logger.warn("Redis connection problem", :exception => e) + # Reset the redis variable to trigger reconnect + @redis = nil + # this sleep does not need to be stoppable as its + # in a while !stop? loop + sleep 1 end - else - item = redis.blpop(@key, 0, :timeout => 1) - return unless item # from timeout or other conditions + end + end - # blpop returns the 'key' read from as well as the item result - # we only care about the result (2nd item in the list). - queue_event(item.last, output_queue) - end + def sortedset_batch_listener(redis, output_queue) + begin + results = redis.evalsha(@redis_script_sha, [@key], [@batch_count-1]) + results.each do |item| + queue_event(item, output_queue) + end + if results.size.zero? + sleep BATCH_EMPTY_SLEEP + end + rescue ::Redis::CommandError => e + if e.to_s =~ /NOSCRIPT/ then + @logger.warn("Redis may have been restarted, reloading Redis batch EVAL script", :exception => e); + sortedset_load_batch_script(redis) + retry + else + raise e + end + end end + def sortedset_single_listener(redis, output_queue) + redis.watch(@key) do + if @priority_reverse then + item = redis.zrevrange(@key, 0, 0, :timeout => 1) + else + item = redis.zrange(@key, 0, 0, :timeout => 1) + end + + if item.size.zero? + sleep BATCH_EMPTY_SLEEP + end + + return unless item.size > 0 + + redis.multi do |multi| + redis.zrem(@key, item) + end + + queue_event(item.first, output_queue) + end + end + + # private def subscribe_stop return if @redis.nil? || !@redis.connected? From 2d33d41eb7d2ccc6bc3fc19b56aa25b71b7396be Mon Sep 17 00:00:00 2001 From: Amaury Decreme Date: Fri, 9 Jun 2017 09:43:10 +0200 Subject: [PATCH 3/7] Esthetic --- lib/logstash/inputs/redis.rb | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/lib/logstash/inputs/redis.rb b/lib/logstash/inputs/redis.rb index d96fe22..7328054 100644 --- a/lib/logstash/inputs/redis.rb +++ b/lib/logstash/inputs/redis.rb @@ -4,8 +4,7 @@ require "logstash/inputs/threadable" require 'redis' -# This input will read events from a Redis instance; it supports both Redis channels and lists -# with or without priority mode. +# This input will read events from a Redis instance; it supports both Redis channels, lists and sortedsets. # # The list command (BLPOP) used by Logstash is supported in Redis v1.3.1+, and # the channel commands used by Logstash are found in Redis v1.3.8+. @@ -13,7 +12,7 @@ # and stability will be found in more recent stable versions. Versions 2.6.0+ # are recommended. # -# The priority 'list' commands (ZREMRANGEBYRANK, ZRANGE, ZREVRANGE) used by Logstash are supported +# The sortedset commands (ZREMRANGEBYRANK, ZRANGE, ZREVRANGE) used by Logstash are supported # in Redis v2.0.0+ # # For more information about Redis, see @@ -46,7 +45,7 @@ module LogStash module Inputs class Redis < LogStash::Inputs::Threadable # The name of a Redis list or channel. config :key, :validate => :string, :required => true - # Pop high scores item first in sortedset. No effect for data_type + # Pop high scores item first in sortedset. No effect for other data types config :priority_reverse, :validate => :boolean, :default => false # Specify either list or channel. If `redis\_type` is `list`, then we will BLPOP @@ -158,14 +157,12 @@ def connect # private def list_load_batch_script(redis) #A Redis Lua EVAL script to fetch a count of keys - redis_script = "local batchsize = tonumber(ARGV[1])\n" - redis_script << "local result = redis.call('" - redis_script << 'lrange' - redis_script << "', KEYS[1], 0, batchsize)\n" - redis_script << "redis.call('" - redis_script << "ltrim', KEYS[1], batchsize + 1, -1" - redis_script << ")\nreturn result\n" - + redis_script = < Date: Mon, 12 Jun 2017 14:15:06 +0200 Subject: [PATCH 4/7] Preparation for more data_type s --- spec/inputs/redis_spec.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/spec/inputs/redis_spec.rb b/spec/inputs/redis_spec.rb index d4b6812..8f75427 100644 --- a/spec/inputs/redis_spec.rb +++ b/spec/inputs/redis_spec.rb @@ -4,7 +4,7 @@ require 'logstash/inputs/redis' require 'securerandom' -def populate(key, event_count) +def list_populate(key, event_count) require "logstash/event" redis = Redis.new(:host => "localhost") event_count.times do |value| @@ -15,7 +15,7 @@ def populate(key, event_count) end end -def process(conf, event_count) +def list_process(conf, event_count) events = input(conf) do |pipeline, queue| event_count.times.map{queue.pop} end @@ -42,8 +42,8 @@ def process(conf, event_count) } CONFIG - populate(key, event_count) - process(conf, event_count) + list_populate(key, event_count) + list_process(conf, event_count) end it "should read events from a list using batch_count (default 125)" do @@ -59,8 +59,8 @@ def process(conf, event_count) } CONFIG - populate(key, event_count) - process(conf, event_count) + list_populate(key, event_count) + list_process(conf, event_count) end end @@ -305,7 +305,7 @@ def close_thread(inst, rt) describe LogStash::Inputs::Redis do context "when using data type" do - ["list", "channel", "pattern_channel"].each do |data_type| + ["list", "channel", "sortedset", "pattern_channel"].each do |data_type| context data_type do it_behaves_like "an interruptible input plugin" do let(:config) { {'key' => 'foo', 'data_type' => data_type } } From 48780318358f7ee27aedb6c4c342c32ccccd8d5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Decr=C3=AAme?= Date: Mon, 12 Jun 2017 14:52:40 +0200 Subject: [PATCH 5/7] Integration tests for sortedset --- spec/inputs/redis_spec.rb | 119 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/spec/inputs/redis_spec.rb b/spec/inputs/redis_spec.rb index 8f75427..add7525 100644 --- a/spec/inputs/redis_spec.rb +++ b/spec/inputs/redis_spec.rb @@ -15,6 +15,33 @@ def list_populate(key, event_count) end end + +def sortedset_populate(key, event_count) + require "logstash/event" + redis = Redis.new(:host => "localhost") + + + event_count_1 = event_count / 2 + event_count_2 = event_count - event_count_1 + + # Add half events in default order + event_count_1.times do |value| + event = LogStash::Event.new("sequence" => value) + Stud.try(10.times) do + redis.zadd(key, value, event.to_json) + end + end + + # Add half events in reverse order + event_count_2.times do |value| + value = event_count - value - 1 + event = LogStash::Event.new("sequence" => value) + Stud.try(10.times) do + redis.zadd(key, value, event.to_json) + end + end +end + def list_process(conf, event_count) events = input(conf) do |pipeline, queue| event_count.times.map{queue.pop} @@ -23,6 +50,22 @@ def list_process(conf, event_count) expect(events.map{|evt| evt.get("sequence")}).to eq((0..event_count.pred).to_a) end +def sortedset_process(conf, event_count) + events = input(conf) do |pipeline, queue| + event_count.times.map{queue.pop} + end + + expect(events.map{|evt| evt.get("sequence")}).to eq((0..event_count.pred).to_a) +end + +def sortedsetrev_process(conf, event_count) + events = input(conf) do |pipeline, queue| + event_count.times.map{queue.pop} + end + + expect(events.map{|evt| evt.get("sequence")}).to eq((0..event_count.pred).to_a.reverse) +end + # integration tests --------------------- describe "inputs/redis", :redis => true do @@ -46,6 +89,45 @@ def list_process(conf, event_count) list_process(conf, event_count) end + it "should read events from a sortedset in default order" do + key = SecureRandom.hex + event_count = 1000 + rand(50) + # event_count = 100 + conf = <<-CONFIG + input { + redis { + type => "blah" + key => "#{key}" + data_type => "sortedset" + batch_count => 1 + } + } + CONFIG + + sortedset_populate(key, event_count) + sortedset_process(conf, event_count) + end + + it "should read events from a sortedset in reverse order" do + key = SecureRandom.hex + event_count = 1000 + rand(50) + # event_count = 100 + conf = <<-CONFIG + input { + redis { + type => "blah" + key => "#{key}" + data_type => "sortedset" + batch_count => 1 + priority_reverse => true + } + } + CONFIG + + sortedset_populate(key, event_count) + sortedsetrev_process(conf, event_count) + end + it "should read events from a list using batch_count (default 125)" do key = SecureRandom.hex event_count = 1000 + rand(50) @@ -62,6 +144,43 @@ def list_process(conf, event_count) list_populate(key, event_count) list_process(conf, event_count) end + + it "should read events from a sortedset in default order using batch_count" do + key = SecureRandom.hex + event_count = 1000 + rand(50) + # event_count = 100 + conf = <<-CONFIG + input { + redis { + type => "blah" + key => "#{key}" + data_type => "sortedset" + } + } + CONFIG + + sortedset_populate(key, event_count) + sortedset_process(conf, event_count) + end + + it "should read events from a sortedset in reverse order using batch_count" do + key = SecureRandom.hex + event_count = 1000 + rand(50) + # event_count = 100 + conf = <<-CONFIG + input { + redis { + type => "blah" + key => "#{key}" + data_type => "sortedset" + priority_reverse => true + } + } + CONFIG + + sortedset_populate(key, event_count) + sortedsetrev_process(conf, event_count) + end end # unit tests --------------------- From 6e6105e210b30f1c24a33e4191e32e1cd3fa7fd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Decr=C3=AAme?= Date: Tue, 13 Jun 2017 11:46:48 +0200 Subject: [PATCH 6/7] Added documentation --- docs/index.asciidoc | 38 ++++++++++++++++++++++++------------ lib/logstash/inputs/redis.rb | 2 +- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/docs/index.asciidoc b/docs/index.asciidoc index 9d0d284..21af6bf 100644 --- a/docs/index.asciidoc +++ b/docs/index.asciidoc @@ -42,10 +42,11 @@ This plugin supports the following configuration options plus the <> |<>|No -| <> |<>, one of `["list", "channel", "pattern_channel"]`|Yes +| <> |<>, one of `["list", "channel", "pattern_channel", "sortedset"]`|Yes | <> |<>|No | <> |<>|No | <> |<>|Yes +| <> |<>|No | <> |<>|No | <> |<>|No | <> |<>|No @@ -58,7 +59,7 @@ input plugins.   [id="plugins-{type}s-{plugin}-batch_count"] -===== `batch_count` +===== `batch_count` * Value type is <> * Default value is `125` @@ -66,18 +67,21 @@ input plugins. The number of events to return from Redis using EVAL. [id="plugins-{type}s-{plugin}-data_type"] -===== `data_type` +===== `data_type` * This is a required setting. - * Value can be any of: `list`, `channel`, `pattern_channel` + * Value can be any of: `list`, `channel`, `pattern_channel`, `sortedset` * There is no default value for this setting. Specify either list or channel. If `redis\_type` is `list`, then we will BLPOP the -key. If `redis\_type` is `channel`, then we will SUBSCRIBE to the key. +key. +Specify either list or channel. If `redis\_type` is `sortedset`, then we will +ZRANGE/ZREVRANGE and ZREM/ZREMRANGEBYRANK the key. +If `redis\_type` is `channel`, then we will SUBSCRIBE to the key. If `redis\_type` is `pattern_channel`, then we will PSUBSCRIBE to the key. [id="plugins-{type}s-{plugin}-db"] -===== `db` +===== `db` * Value type is <> * Default value is `0` @@ -85,7 +89,7 @@ If `redis\_type` is `pattern_channel`, then we will PSUBSCRIBE to the key. The Redis database number. [id="plugins-{type}s-{plugin}-host"] -===== `host` +===== `host` * Value type is <> * Default value is `"127.0.0.1"` @@ -93,16 +97,24 @@ The Redis database number. The hostname of your Redis server. [id="plugins-{type}s-{plugin}-key"] -===== `key` +===== `key` * This is a required setting. * Value type is <> * There is no default value for this setting. -The name of a Redis list or channel. +The name of a Redis list, sortedset or channel. + +[id="plugins-{type}s-{plugin}-priority_reverse"] +===== `priority_reverse` + + * Value type is <> + * Default value if `false` + +When the data_type is `sortedset`, read the sortedset in reverse order. [id="plugins-{type}s-{plugin}-password"] -===== `password` +===== `password` * Value type is <> * There is no default value for this setting. @@ -110,7 +122,7 @@ The name of a Redis list or channel. Password to authenticate with. There is no authentication by default. [id="plugins-{type}s-{plugin}-port"] -===== `port` +===== `port` * Value type is <> * Default value is `6379` @@ -118,7 +130,7 @@ Password to authenticate with. There is no authentication by default. The port to connect on. [id="plugins-{type}s-{plugin}-threads"] -===== `threads` +===== `threads` * Value type is <> * Default value is `1` @@ -126,7 +138,7 @@ The port to connect on. [id="plugins-{type}s-{plugin}-timeout"] -===== `timeout` +===== `timeout` * Value type is <> * Default value is `5` diff --git a/lib/logstash/inputs/redis.rb b/lib/logstash/inputs/redis.rb index 7328054..bdde9dc 100644 --- a/lib/logstash/inputs/redis.rb +++ b/lib/logstash/inputs/redis.rb @@ -12,7 +12,7 @@ # and stability will be found in more recent stable versions. Versions 2.6.0+ # are recommended. # -# The sortedset commands (ZREMRANGEBYRANK, ZRANGE, ZREVRANGE) used by Logstash are supported +# The sortedset commands (ZREM, ZREMRANGEBYRANK, ZRANGE, ZREVRANGE) used by Logstash are supported # in Redis v2.0.0+ # # For more information about Redis, see From 8ab9fbca58d862dfa9ba6f94b440ec1c5554db84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Decr=C3=AAme?= Date: Tue, 13 Jun 2017 11:48:35 +0200 Subject: [PATCH 7/7] Fix typo --- docs/index.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.asciidoc b/docs/index.asciidoc index 21af6bf..2433734 100644 --- a/docs/index.asciidoc +++ b/docs/index.asciidoc @@ -108,7 +108,7 @@ The name of a Redis list, sortedset or channel. [id="plugins-{type}s-{plugin}-priority_reverse"] ===== `priority_reverse` - * Value type is <> + * Value type is <> * Default value if `false` When the data_type is `sortedset`, read the sortedset in reverse order.