Skip to content

Commit

Permalink
feature(unique_values_list): fast (non blocking) unique values list a…
Browse files Browse the repository at this point in the history
…dded

- refactoring unique_values_list;
- fast (non blocking) unique values list added;
- unique_list[:list_class] property introduced on UniqueHashCounter.
  • Loading branch information
Napolskih committed Sep 12, 2013
1 parent 897418d commit addbb40
Show file tree
Hide file tree
Showing 11 changed files with 313 additions and 157 deletions.
37 changes: 31 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,17 @@ redis:
}
```

## RedisCounters::UniqueValuesList
## RedisCounters::UniqueValuesLists::Standard

Список уникальных значений, с возможностью группировки и партиционирования значений.
Помимо списка значений, ведет так же, список партиций, для каждой группы.

Особенности:
- Использует механизм оптимистичных блокировок.
- Помимо списка значений, ведет так же, список партиций, для каждой группы.
- Полностью транзакционен - сторонний блок, выполняемый после добавления уникального элемента,
выполняется в той же транзакции, в которой добавляется уникальный элемент.

Вероятно, в условиях большой конкурентности, обладает не лучшей производительносью из-за частых блокировок.

Обязательные параметры: counter_name и value_keys.

Expand All @@ -93,7 +100,7 @@ redis:

Простой список уникальных пользователей.
```ruby
counter = RedisCounters::UniqueValuesList.new(redis, {
counter = RedisCounters::UniqueValuesLists::Standard.new(redis, {
:counter_name => :users,
:value_keys => [:user_id]
})
Expand All @@ -108,7 +115,7 @@ redis:

Список уникальных пользователей, посетивших компаниию, за месяц, сгруппированный по суткам.
```ruby
counter = RedisCounters::UniqueValuesList.new(redis, {
counter = RedisCounters::UniqueValuesLists::Standard.new(redis, {
:counter_name => :company_users_by_month,
:value_keys => [:company_id, :user_id],
:group_keys => [:start_month_date],
Expand All @@ -130,13 +137,30 @@ redis:
company_users_by_month:2013-09-01:2013-09-05 = ['1:22']
```

## RedisCounters::UniqueValuesLists::Fast

Быстрый список уникальных значений, с возможностью группировки и партиционирования значений.

Скорость работы достигается за счет следующих особенностей:
- Использует 2х объема памяти для хранения элементов,
при использовании партиционирования.
Eсли партиционирование не используется, то расход памяти такой-же как у UniqueValuesLists::Standard.
- Не транзакционен - сторонний блок, выполняемый после добавления уникального элемента,
выполняется за пределами транзакции, в которой добавляется уникальный элемент.
- Не ведется список партиций.

Обязательные параметры: counter_name и value_keys.

### Сложность
+ добавление элемента - O(1)

## RedisCounters::UniqueHashCounter

Структура на основе двух предыдущих.
Сборная конструкция на основе предыдущих.
HashCounter, с возможностью подсчета только у уникальных событий.

### Сложность
аналогично UniqueValuesList.
аналогично сложности, используемого уникального списка.

### Примеры использования

Expand All @@ -147,6 +171,7 @@ counter = RedisCounters::UniqueHashCounter.new(redis, {
:group_keys => [:company_id],
:partition_keys => [:date],
:unique_list => {
:list_class => RedisCounters::UniqueValuesLists::Standard
:value_keys => [:company_id, :user_id],
:group_keys => [:start_month_date],
:partition_keys => [:date]
Expand Down
4 changes: 3 additions & 1 deletion lib/redis_counters.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
require 'redis_counters/base_counter'
require 'redis_counters/hash_counter'
require 'redis_counters/unique_hash_counter'
require 'redis_counters/unique_values_list'
require 'redis_counters/unique_values_lists/base'
require 'redis_counters/unique_values_lists/standard'
require 'redis_counters/unique_values_lists/fast'

require 'active_support/core_ext'

Expand Down
7 changes: 5 additions & 2 deletions lib/redis_counters/unique_hash_counter.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# coding: utf-8
require 'redis_counters/hash_counter'
require 'redis_counters/unique_values_list'

module RedisCounters

Expand All @@ -17,7 +16,7 @@ def process_value

def init
super
@unique_values_list = UniqueValuesList.new(
@unique_values_list = unique_values_list_class.new(
redis,
unique_values_list_options
)
Expand All @@ -30,6 +29,10 @@ def unique_values_list_options
def unique_values_list_name
[counter_name, UNIQUE_LIST_POSTFIX].join(KEY_DELIMITER)
end

def unique_values_list_class
unique_values_list_options.fetch(:list_class).to_s.constantize
end
end

end
122 changes: 0 additions & 122 deletions lib/redis_counters/unique_values_list.rb

This file was deleted.

47 changes: 47 additions & 0 deletions lib/redis_counters/unique_values_lists/base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# coding: utf-8
require 'redis_counters/base_counter'

module RedisCounters
module UniqueValuesLists

class Base < RedisCounters::BaseCounter
alias_method :add, :process

protected

def key(partition = partition_params)
[counter_name, group_params, partition].flatten.compact.join(KEY_DELIMITER)
end

def group_params
group_keys.map { |key| params.fetch(key) }
end

def partition_params
partition_keys.map { |key| params.fetch(key) }
end

def value
value_params = value_keys.map { |key| params.fetch(key) }
value_params.join(KEY_DELIMITER)
end

def use_partitions?
partition_keys.present?
end

def value_keys
@value_keys ||= Array.wrap(options.fetch(:value_keys))
end

def partition_keys
@partition_keys ||= Array.wrap(options.fetch(:partition_keys, []))
end

def group_keys
@group_keys ||= Array.wrap(options.fetch(:group_keys, []))
end
end

end
end
37 changes: 37 additions & 0 deletions lib/redis_counters/unique_values_lists/fast.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# coding: utf-8
require 'redis_counters/unique_values_lists/base'

module RedisCounters
module UniqueValuesLists

class Fast < UniqueValuesLists::Base

protected

def process_value
return unless add_value
yield redis if block_given?
true
end

def add_value
return unless redis.sadd(main_partition_key, value)
redis.sadd(current_partition_key, value) if use_partitions?
true
end

def main_partition_key
key([])
end

def current_partition_key
key
end

def partitions
redis.keys(key('*'))
end
end

end
end
Loading

0 comments on commit addbb40

Please sign in to comment.