ualbertalib / discovery

Discovery is the University of Alberta Libraries' catalogue interface, built using Blacklight
http://search.library.ualberta.ca
12 stars 3 forks source link

DB tables hoarding data #1361

Open nmacgreg opened 6 years ago

nmacgreg commented 6 years ago

Description In Production, the database grows without bound. See the graph of the database growth, since we built a new DB server, back in June, 2018

Expected behavior This application must not allow its database to grow without bound. That way lies madness.

Additional context This application already includes a rake task ("sessions:cleanup"), implemented as a crontab, via ansible, for keeping the sessions table clean. But there are more tables that need to be cleaned up:

MariaDB [blacklight_prod]> select count() from users; +----------+ | count() | +----------+ | 2464435 | +----------+ 1 row in set (6.76 sec) MariaDB [blacklight_prod]> select count() from searches; +----------+ | count() | +----------+ | 3188677 | +----------+ 1 row in set (2.74 sec)

murny commented 6 years ago

Thanks Neil for the detective work here! Agree that this is a big problem.

Regarding sessions, we shouldn't be storing this in the database. The default for Rails for the past 5+ years has been storing the user session in a cookie, like most web frameworks in the world. You actually have to go out of your way to get the sessions to be stored in the database as this is no longer supported in Rails and has been ripped out into a separate gem: https://github.com/rails/activerecord-session_store and has been added to discovery here: https://github.com/ualbertalib/discovery/blob/master/Gemfile#L4

This was ripped out of Rails for the reasons you have experienced first hand. That the major issue with storing sessions in the database is it's not scalable. It puts an unnecessary load on your database. Once your application receives a large amount of traffic, the sessions database table is continuously bombarded with read/write operations.

(NOTE: Jupiter/NEOSdiscovery are done properly and don't store sessions in the database)

Regarding the users and searches tables, these are both Blacklight problems.

Blacklight will create a new user in the database for every new session/visit to the site via this gem from samvera/blacklight community: https://github.com/cbeer/devise-guests (this is used mainly for the "bookmarks" feature of blacklight)

Blacklight will also save every single search a user makes and make them viewable here: https://library.ualberta.ca/search_history (and funny enough the "clear search history" button doesn't actually delete the entries from the database, just clears the users session) or https://library.ualberta.ca/saved_searches (for logged in users which isn't really supported in discovery)

Both of these "features" and how they been implemented from Blacklight are pretty insane and probably no one actually uses these in discovery. Which is another great example of how over bloated Blacklight is and how little of it we actually use.

Regarding quick fixes, we can do something just like our custom rake task and cronjob for clearing out session data. As not surprisingly, these rake tasks already exist in the code base:

rake blacklight:delete_old_searches[days_old]            # Removes entries in the searches table that are older than the number of days given
rake devise_guests:delete_old_guest_users[days_old]      # Removes entries in the users table for guest users that are older than the number of days given
seanluyk commented 6 years ago

This is concerning to me from a privacy perspective, as it's not in line with our privacy policy. We don't generally keep user session data. In terms of the bookmarks and saved searches features, usability testing revealed that users had other methods to save records, and had trouble finding these features in the first place (possibly due to UI issues). Once we're at the stage where we're developing requirements for the next phase of Discovery, these are the kinds of features I'd like to verify with users that they actually want/need them

nmacgreg commented 6 years ago

Whul, OK. Feels like in the time it took to respond & write the above, this could have been fixed. Can we bump this in priority? Because it just hit me in Production, AGAIN. With 4558869 lines in the 'searches' table today, it overran the allocated disk space for this database. OK, good news: monitoring is working. OK, more good news: 4.5m searches in less than 30 days. However, this is costing us actual hard dollars, because we pay per-gigabyte for CommVault backup. I'm also going to have to figure out how to shrink a tablespace on a MySQL InnoDB file.

ARGH - I detest the concept of using GitHub Issues as a communication method! Reading backwards, I'm really frustrated to see that murny offered a solution to the problem on Oct 30, but I didn't know about it, so I couldn't take action. I'm here dropping issues because your team requested it. I'm frustrated you won't reciprocate & use my team's preferred communication methods - email in this case, OTRS tickets when appropriate.

nmacgreg commented 6 years ago

It's also worth saying, something dramatic happened in week 46 - the rate of growth went exponential (or at least hockey-stick) in wk46. Please review the graph in Nagios, for /var/lib/mysql, server maghull.

weiweishi commented 6 years ago

Discussed this with @seanluyk and @kgood It feels that if we schedule the two cron jobs suggested in the post above, the urgency of this issue will be elevated. @nmacgreg you should have received emails from github as you are the one opened the issue, unless you configure your github settings differently? We will make time for us to compare notes github communication - in the way that satisfy both teams' needs.

nmacgreg commented 5 years ago

During CR840, I suspect this issue caused an unexpected (but brief) Prod outage this morning, probably because the 'searches' table had several million rows, impacting the database migration step that was a key goal of this CR.

After the dust had settled, I tried out the two suggested rake tasks: the first one works fine, and I'm implementing it now in the ansible playbook. However, the second one failed:

[root@forest blacklight]# bundle exec rake devise_guests:delete_old_guest_users[2] rake aborted! ActiveRecord::StatementInvalid: *Mysql2::Error: Unknown column 'guest' in 'where clause': SELECT users. FROM users WHERE (guest = 1 and updated_at < '2019-10-30 15:31:39')** /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/mysql2-0.4.10/lib/mysql2/client.rb:120:in _query' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/mysql2-0.4.10/lib/mysql2/client.rb:120:inblock in query' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/mysql2-0.4.10/lib/mysql2/client.rb:119:in handle_interrupt' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/mysql2-0.4.10/lib/mysql2/client.rb:119:inquery' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/activerecord-4.2.11.1/lib/active_record/connection_adapters/abstract_mysql_adapter.rb:317:in block in execute' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/activerecord-4.2.11.1/lib/active_record/connection_adapters/abstract_adapter.rb:484:inblock in log' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/activesupport-4.2.11.1/lib/active_support/notifications/instrumenter.rb:20:in instrument' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/activerecord-4.2.11.1/lib/active_record/connection_adapters/abstract_adapter.rb:478:inlog' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/activerecord-4.2.11.1/lib/active_record/connection_adapters/abstract_mysql_adapter.rb:317:in execute' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/activerecord-4.2.11.1/lib/active_record/connection_adapters/mysql2_adapter.rb:217:inexecute' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/activerecord-4.2.11.1/lib/active_record/connection_adapters/mysql2_adapter.rb:221:in exec_query' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/activerecord-4.2.11.1/lib/active_record/connection_adapters/abstract/database_statements.rb:356:inselect' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/activerecord-4.2.11.1/lib/active_record/connection_adapters/abstract/database_statements.rb:32:in select_all' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/activerecord-4.2.11.1/lib/active_record/connection_adapters/abstract/query_cache.rb:70:inselect_all' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/activerecord-4.2.11.1/lib/active_record/querying.rb:39:in find_by_sql' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/activerecord-4.2.11.1/lib/active_record/relation.rb:639:inexec_queries' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/activerecord-4.2.11.1/lib/active_record/relation.rb:515:in load' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/activerecord-4.2.11.1/lib/active_record/relation.rb:243:into_a' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/activerecord-4.2.11.1/lib/active_record/relation/delegation.rb:46:in each' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/devise-guests-0.7.0/lib/railties/devise_guests.rake:9:inblock (2 levels) in <top (required)>' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/rake-12.3.3/exe/rake:27:in <top (required)>' /usr/bin/bundle:23:inload' /usr/bin/bundle:23:in <main>' Caused by: Mysql2::Error: Unknown column 'guest' in 'where clause' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/mysql2-0.4.10/lib/mysql2/client.rb:120:in_query' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/mysql2-0.4.10/lib/mysql2/client.rb:120:in block in query' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/mysql2-0.4.10/lib/mysql2/client.rb:119:inhandle_interrupt' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/mysql2-0.4.10/lib/mysql2/client.rb:119:in query' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/activerecord-4.2.11.1/lib/active_record/connection_adapters/abstract_mysql_adapter.rb:317:inblock in execute' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/activerecord-4.2.11.1/lib/active_record/connection_adapters/abstract_adapter.rb:484:in block in log' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/activesupport-4.2.11.1/lib/active_support/notifications/instrumenter.rb:20:ininstrument' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/activerecord-4.2.11.1/lib/active_record/connection_adapters/abstract_adapter.rb:478:in log' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/activerecord-4.2.11.1/lib/active_record/connection_adapters/abstract_mysql_adapter.rb:317:inexecute' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/activerecord-4.2.11.1/lib/active_record/connection_adapters/mysql2_adapter.rb:217:in execute' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/activerecord-4.2.11.1/lib/active_record/connection_adapters/mysql2_adapter.rb:221:inexec_query' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/activerecord-4.2.11.1/lib/active_record/connection_adapters/abstract/database_statements.rb:356:in select' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/activerecord-4.2.11.1/lib/active_record/connection_adapters/abstract/database_statements.rb:32:inselect_all' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/activerecord-4.2.11.1/lib/active_record/connection_adapters/abstract/query_cache.rb:70:in select_all' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/activerecord-4.2.11.1/lib/active_record/querying.rb:39:infind_by_sql' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/activerecord-4.2.11.1/lib/active_record/relation.rb:639:in exec_queries' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/activerecord-4.2.11.1/lib/active_record/relation.rb:515:inload' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/activerecord-4.2.11.1/lib/active_record/relation.rb:243:in to_a' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/activerecord-4.2.11.1/lib/active_record/relation/delegation.rb:46:ineach' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/devise-guests-0.7.0/lib/railties/devise_guests.rake:9:in block (2 levels) in <top (required)>' /var/www/sites/blacklight/vendor/ruby/2.5.0/gems/rake-12.3.3/exe/rake:27:in<top (required)>' /usr/bin/bundle:23:in load' /usr/bin/bundle:23:in

' Tasks: TOP => devise_guests:delete_old_guest_users (See full trace by running task with --trace)

nmacgreg commented 5 years ago

I have established a work-around for this problem.

'sessions' was completed long ago.

I've (finally) implemented a cronjob calling a rake task for 'searches':

Ansible: Clean searches table, hourly

0 source /root/.bashrc && cd /var/www/sites/blacklight && RAILS_ENV=production bundle exec rake blacklight:delete_old_searches[2] > /tmp/searchesCleanup 2>&1

And for 'users' I built a one-off custom cronjob on maghull to delete entries.

Results:

MariaDB [blacklight_prod]> select count() from blacklight_prod.sessions; +----------+ | count() | +----------+ | 1945 | +----------+ 1 row in set (0.00 sec)

MariaDB [blacklight_prod]> select count() from blacklight_prod.users; +----------+ | count() | +----------+ | 13481 | +----------+ 1 row in set (0.00 sec)

MariaDB [blacklight_prod]> select count() from blacklight_prod.searches; +----------+ | count() | +----------+ | 40468 | +----------+ 1 row in set (0.00 sec)

I recommend leaving this open until resolved.