<%= link_to(submission.title, submission.url) %> |
<%= submission.url %> |
<%= submission.community.name %> |
<%= link_to("#{submission.comments.size} comments",
submission, class: 'btn btn-primary btn-xs') %>
|
<% end %>
```
---
# Generated SQL Statements
```
Processing by SubmissionsController#index as HTML
Submission Load (0.5ms) SELECT `submissions`.* FROM `submissions`
Community Load (0.3ms) SELECT `communities`.* FROM `communities` WHERE `communities`.`id` = 1 LIMIT 1
SELECT COUNT(*) FROM `comments` WHERE `comments`.`submission_id` = 1
SELECT `communities`.* FROM `communities` WHERE `communities`.`id` = 1 LIMIT 1 [["id", 1]]
SELECT COUNT(*) FROM `comments` WHERE `comments`.`submission_id` = 2
SELECT `communities`.* FROM `communities` WHERE `communities`.`id` = 1 LIMIT 1 [["id", 1]]
SELECT COUNT(*) FROM `comments` WHERE `comments`.`submission_id` = 3
SELECT `communities`.* FROM `communities` WHERE `communities`.`id` = 1 LIMIT 1 [["id", 1]]
...
SELECT `communities`.* FROM `communities` WHERE `communities`.`id` = 20 LIMIT 1 [["id", 20]]
SELECT COUNT(*) FROM `comments` WHERE `comments`.`submission_id` = 400
```
--
## That is a lot of `SELECT` queries!
---
# Number of Queries Bottleneck
We are issuing a ton of `SELECT` queries. The overhead associated with each is
slowing us down.
> How can we fix this problem?
--
## Issue fewer queries.
* Do not ask for the community each time
* Do not ask for the number of comments each time
---
# Reducing (N+1) Queries in Rails
(__Before__) Without `includes`
```ruby
class SubmissionsController < ApplicationController
def index
@submissions = Submission.all
end
end
```
--
(__After__) With `includes`
```ruby
class SubmissionsController < ApplicationController
def index
@submissions = Submission.includes(:comments)
.includes(:community).all
end
end
```
--
Result
```
Result: ActiveRecord 39.6ms
Submission Load (0.9ms) SELECT `submissions`.* FROM `submissions`
Comment Load (38.3ms) SELECT `comments`.* FROM `comments` WHERE `comments`.`submission_id` IN (1, 2,... 399, 400)
Community Load (0.4ms) SELECT `communities`.* FROM `communities` WHERE `communities`.`id` IN (1, 2, ...19, 20)
```
---
# SQL Explain
Sometimes things are still slow even when the number of queries is minimized.
SQL provides an `EXPLAIN` statement that can be used to analyze individual
queries.
When a query starts with `EXPLAIN`...
* the query is not actually executed
* the produced output will help us identify potential improvements
* e.g. sequential vs index scan, startup vs total cost
https://www.postgresql.org/docs/9.1/sql-explain.html
---
# SQL Optimizations
There are three primary ways to optimize SQL queries:
--
* Add or modify indexes
--
* Modify the table structure
--
* Directly optimize the query
---
# SQL Indexes
> What is an index?
--
An index is a fast, ordered, compact structure (often B-tree) for identifying row
locations.
--
When an index is provided on a column that is to be filtered (searching for a
particular item), the database is able to quickly find that information.
--
Indexes can exist on a single column, or across multiple columns. Multi-column
indexes are useful when filtering on two columns (e.g., CS classes that are not
full).
---
# Adding Indexes in Rails
To add an index on the `name` field of the `Product` table, create a migration
containing:
```ruby
class AddNameIndexProducts < ActiveRecord::Migration
def change
add_index :products, :name
end
end
```
---
# Related: Foreign Keys
By default, when dealing with relationships between ActiveRecord objects, Rails
will validate the constraints in the application layer.
For example, an `Employee` object should have a `Company` that they work for.
Assuming the relationship is defined properly, Rails will enforce that when
creating an `Employee`, the associated `Company` exists.
Many databases, have built-in support for enforcing such constraints. With
rails, one can also take advantage of the database's foreign key support via
[add_foreign_key](http://guides.rubyonrails.org/active_record_migrations.html#foreign-keys).
```ruby
class AddForeignKeyToOrders < ActiveRecord::Migration
def change
add_foreign_key :employees, :companies
end
end
```
---
# Optimize the Table Structure
Indexes work best when they can be kept in memory.
Sometimes changing the field type, or index length can provide significant
memory savings.
If appropriate some options are:
* Reduce the length of a VARCHAR index if appropriate
* Use a smaller unsigned integer type
* Use an integer or enum field for statuses rather than a text-based value
---
# Directly Optimize the Query
Before
```sql
explain select count(*) from txns where parent_id - 1600 = 16340;
select_type: SIMPLE
table: txns
type: index
key: index_txns_on_reverse_txn_id
rows: 439186
Extra: Using where; Using index
```
--
After
```sql
explain select count(*) from txns where parent_id = 16340 + 1600
select_type: SIMPLE
table: txns
type: const
key: index_txns_on_reverse_txn_id
rows: 1
Extra: Using index
```
---
# Summary
* understand the importance of relational databases in architecting scalable
Internet systems
* understand how to design with concurrency in mind
* have a basic idea regarding how to speed up the data layer
- determine where to start
- understand which rails-specific techniques to use
- identify the source of slowness in an SQL query
---