Before Rails 6,
keys with the _html suffix in the language locale files
are automatically marked as HTML safe.
These HTML safe keys do not get escaped when used in the views.
Once rendered, this page looks like this.
This way of marking translations as HTML safe
adding _html suffix to the keys
does not work as expected
the value is an array.
The rendered page escapes the unsafe HTML
rendering the array of translations
for the key .sections.services.list_html
even though that key has the _html suffix.
to manually mark all the translations
in that array as HTML safe
using the methods such as #raw or #html_safe.
Arrays of translations are trusted as HTML safe by using the ‘_html’ suffix in Rails 6
In Rails 6,
the unexpected behavior
not marking an array of translations as HTML safe
even though the key of that array has the _html suffix
We can see above that
we no longer need to manually mark the translations as HTML safe
for the key .sections.services.title_html
using the methods such as #raw or #html_safe
since that key has the _html suffix.
A lot of times, we ask user for sensitive data such as password, credit card number
etc. We should not be able to see this information in logs. So, there must be a way
in Rails to filter out these parameters from logs.
However there is still a security issue when we call inspect on an ActiveRecord
object for logging purposes. In this case, Rails does not consider
and displays the sensitive information.
Recyclable cache keys
or cache versioning
was introduced in Rails 5.2.
frequently need to invalidate their cache
because cache store has limited memory.
We can optimize cache storage
and minimize cache miss
using recyclable cache keys.
Recyclable cache keys
is supported by all
that ship with Rails.
Before Rails 5.2,
cache_key’s format was
Here model_name and id
are always constant
for an object
changes on every update.
In Rails 5.2,
and new method #cache_version
Let’s update post instance
and check cache_key
and cache_version’s behaviour.
To use cache versioning feature,
we have to
is set to false
for backward compatibility.
We can enable cache versioning
configuration globally as shown below.
Cache versioning config can
be applied at model level.
Let’s understand the problem
step by step
with cache keys
before Rails 5.2.
Cache versioning works similarly
In case of ActiveRecord::Relation,
if number of records change
and/or record(s) are updated, then
same cache_key is written to cache store
with new cache_version and updated records.
Previously, cache invalidation
had to be done manually
either by deleting cache or
setting cache expire duration.
Cache versioning invalidates
stale data automatically
and keeps latest copy
saving on storage
and performance drastically.
Rails 6 has added support
to provide optimizer hints.
What is Optimizer Hints?
Many relational database management systems (RDBMS)
have a query optimizer.
The job of the query optimizer
to determine the most efficient and fast plan
to execute a given SQL query.
Query optimizer has to consider
all possible query execution plans
before it can determine which plan is the optimal plan
for executing the given SQL query
then compile and execute that query.
An optimal plan is chosen by the query optimizer
by calculating the cost of each possible plans.
when the number of tables referenced in a join query increases,
then the time spent in query optimization grows exponentially
which often affects the system’s performance.
The fewer the execution plans
the query optimizer needs to evaluate,
the lesser time is spent in compiling and executing the query.
As an application designer,
we might have more context
about the data stored in our database.
With the contextual knowledge about our database,
we might be able to choose a more efficient execution plan
than the query optimizer.
This is where the optimizer hints or optimizer guidelines
come into picture.
Optimizer hints allow us
to control the query optimizer
to choose a certain query execution plan
based on the specific criteria.
In other words,
we can hint the optimizer
to use or ignore certain optimization plans
using optimizer hints.
optimizer hints should be provided
only when executing a complex query
involving multiple table joins.
Note that the optimizer hints
only affect an individual SQL statement.
To alter the optimization strategies at the global level,
there are different mechanisms supported by different databases.
Optimizer hints provide
finer control over other mechanisms
which allow altering optimization plans by other means.
This produces the same SQL query as above
the result is of type ActiveRecord::Relation.
In PostgreSQL (using the pg_hint_plan extension),
the optimizer hints have a different syntax.
Please checkout the documentation of each database separately
to learn the support and syntax of optimizer hints.
To learn more,
checkout this PR
which introduced the #optimization_hints method
to Rails 6.
Bonus example: Using optimizer hints to speedup a slow SQL statement in MySQL
Consider that we have articles table
with some indexes.
Let’s try to fetch all the articles
which have been published in the last 2 months.
Let’s use EXPLAIN
why it is taking 10.5ms
to execute this query.
According to the above table,
it appears that the query optimizer
is considering users table first
then the articles table.
The rows column indicates
the estimated number of rows the query optimizer must examine
to execute the query.
an estimated percentage of table rows
that will be filtered by the table condition.
The formula rows x filtered
gives the number of rows
that will be joined with the following table.
For users table,
the number of rows to be joined
with the following table is 2 x 100% = 2,
For articles table,
the number of rows to be joined
with the following table is 500 * 7.79 = 38.95.
Since the articles tables contain more records
which references very few records from the users table,
it would be better to consider the articles table first
then the users table.
We can hint MySQL to consider the articles table first as follows.
Note that it took 2.2ms now
fetch the same records
by providing JOIN_ORDER(articles, users) optimization hint.
Let’s try to EXPLAIN
by using this JOIN_ORDER(articles, users) optimization hint.
The result of the EXPLAIN query shows that
the articles table was considered first
then the users table as expected.
We can also see that
the index_articles_on_published_at index key
was considered from the possible keys
to execute the given query.
The filtered column for both tables shows that
the number of filtered rows was 100%
which means no filtering of rows occurred.
We hope this example helps
how to use #explain
in order to investigate and debug the performance issues
then fixing it.
added allocations feature
Using this feature,
an event subscriber can see
how many number of objects were allocated
during the event’s start time and end time.
We have written in detail about this feature
By taking the benefit of this feature,
Rails 6 now reports the allocations made
rendering a view template,
Notice the Allocations: information in the above logs.
We can see that
6 objects were allocated while rendering
shared/_ad_banner.html.erb view partial,
805 objects were allocated while rendering
a collection of 100 articles/_article.html.erb view partials,
and 3901 objects were allocated
while rendering articles/index.html.erb view template.
We can use this information
understand how much time was spent
while rendering a view template
how many objects were allocated in the process’ memory
between the time when that view template had started rendering
the time when that view template had finished rendering.
Before Rails 6,
we have to provide a custom block
to perform custom logging
around retries and discards
of the jobs defined using Active Job framework.
Notice the custom blocks provided
to retry_on and discard_on methods
to an individual job
in the above example.
Extracting such custom logic
to a base class or to a 3rd-party gem is possible
it will be non-standard and will be a bit difficult task.
An alternative approach is
to the hooks instrumented using Active Support Instrumentation API
which is a standard and recommended way.
Prior versions of Rails 6 already instruments
enqueue_at.active_job, enqueue.active_job, perform_start.active_job,
Unfortunately no hook is instrumented
around retries and discards of an Active Job
prior to Rails 6.
Rails 6 has introduced hooks
to Active Job
around retries and discards
to which one can easily subscribe
using Active Support Instrumentation API
to perform custom logging and monitoring or to collect any custom information.
The newly introduced hooks are
Let’s discuss each of these hooks in detail.
Note that whenever we say a job,
it means a job of type ActiveJob.
The enqueue_retry.active_job hook
instrumented when a job
enqueued to retry again
due to occurrence of an exception which
is configured using the retry_on method
in the job’s definition.
This hook is triggered
only when above condition is satisfied
the number of executions of the job
less than the number of attempts
defined using the retry_on method.
The number of attempts is by default set to 5
if not defined explicitly.
This is how we would subscribe to this hook
perform custom logging in our Rails application.
Note that the BackgroundJob::Logger above is our custom logger.
If we want, we can add any other logic instead.
We will change the definition of Container::DeleteJob job as below.
Let’s enqueue this job.
Assume that this job keeps throwing Timeout::Error exception
due to a network issue.
The job will be retried twice
since it is configured to retry
when a Timeout::Error exception occurs
up to maximum 3 attempts.
While retrying this job,
Active Job will instrument enqueue_retry.active_job hook
along with the necessary job payload.
Since we have already subscribed to this hook,
our subscriber would log something like this
with the help of BackgroundJob::Logger.log.