summaryrefslogtreecommitdiff
path: root/db/post_migrate/20170324160416_migrate_user_activities_to_users_last_activity_on.rb
blob: cb1b4f1855da9c998f7a4cf633c0e08082e6d52d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
class MigrateUserActivitiesToUsersLastActivityOn < ActiveRecord::Migration
  include Gitlab::Database::MigrationHelpers

  disable_ddl_transaction!

  DOWNTIME = false
  USER_ACTIVITY_SET_KEY = 'user/activities'.freeze
  ACTIVITIES_PER_PAGE = 100
  TIME_WHEN_ACTIVITY_SET_WAS_INTRODUCED = Time.utc(2016, 12, 1)

  def up
    return if activities_count(TIME_WHEN_ACTIVITY_SET_WAS_INTRODUCED, Time.now).zero?

    day = Time.at(activities(TIME_WHEN_ACTIVITY_SET_WAS_INTRODUCED, Time.now).first.second)

    transaction do
      while day <= Time.now.utc.tomorrow
        persist_last_activity_on(day: day)
        day = day.tomorrow
      end
    end
  end

  def down
    # This ensures we don't lock all users for the duration of the migration.
    update_column_in_batches(:users, :last_activity_on, nil) do |table, query|
      query.where(table[:last_activity_on].not_eq(nil))
    end
  end

  private

  def persist_last_activity_on(day:, page: 1)
    activities_count = activities_count(day.at_beginning_of_day, day.at_end_of_day)

    return if activities_count.zero?

    activities = activities(day.at_beginning_of_day, day.at_end_of_day, page: page)

    update_sql =
      Arel::UpdateManager.new(ActiveRecord::Base)
        .table(users_table)
        .set(users_table[:last_activity_on] => day.to_date)
        .where(users_table[:username].in(activities.map(&:first)))
        .to_sql

    connection.exec_update(update_sql, self.class.name, [])

    unless last_page?(page, activities_count)
      persist_last_activity_on(day: day, page: page + 1)
    end
  end

  def users_table
    @users_table ||= Arel::Table.new(:users)
  end

  def activities(from, to, page: 1)
    Gitlab::Redis::SharedState.with do |redis|
      redis.zrangebyscore(USER_ACTIVITY_SET_KEY, from.to_i, to.to_i,
        with_scores: true,
        limit: limit(page))
    end
  end

  def activities_count(from, to)
    Gitlab::Redis::SharedState.with do |redis|
      redis.zcount(USER_ACTIVITY_SET_KEY, from.to_i, to.to_i)
    end
  end

  def limit(page)
    [offset(page), ACTIVITIES_PER_PAGE]
  end

  def total_pages(count)
    (count.to_f / ACTIVITIES_PER_PAGE).ceil
  end

  def last_page?(page, count)
    page >= total_pages(count)
  end

  def offset(page)
    (page - 1) * ACTIVITIES_PER_PAGE
  end
end