This is a long list, so better get ready!
To eliminate n+ one queries, use includes or eager_load.
This is something that every Rails front end developer should be familiar with, but its still important to mention.
Includes is the best way to query a list that spans multiple tables. Product. Map To get around this, simply change it to Product. Includes (: brand), which would tell Rails to eager load the list of brands first.
Although the initial query is more complicated, its better not to run multiple SQL statements if you can get all of the data you need in one place.
To find optimization problems, use the bullet gem in your fullstack development company.
This gem is highly recommended to be included at the beginning of any Rails project in order to detect these issues early.
Batch inserts and Updates
Similar to the tip, dont loop with multiple SQL statements while creating or updating records. In one case, we needed to update or insert thousands of records every few minutes.
It was painful and difficult to do this in a loop.
Rails 6 now supports bulk inserts. There are three options: insert_all and update_all. You can also use the active record-import gem if you are using an older version.
Learn the differences between common Ruby methods
Although they dont usually make or break an application, it is important to be familiar with the differences between the different Ruby methods.
Rails behaviour keeps changing, and this is what causes confusion for many Rails developers. Rails 5.1 has brought things back to normal.
- present?
- Loads all the records (if not already loaded) and checks if there is at least one record present.
- This is usually very slow if the record is not yet loaded.
- Produces a SELECT *.
- exists?
- Will always query the database to check if at least one record is present.
- This is the quickest for instances when you only need to check for existence, but this can also be the slowest by one whole SQL call if the record is already loaded.
- Produces a SELECT 1 LIMIT 1.
- any?
- Produces a SELECT COUNT(*) in Rails 4.2 and SELECT 1 LIMIT 1 in Rails 5.1 and up.
- Behaves like present? if records are already loaded.
- Behaves like exists? if the records arent loaded.
- Still slower than .exists? in Rails 5.0 and below. For Rails 5.1 and up, any? will generate the same query as .exists?.
-
The rule of thumb
- When only checking for existence, always use exists?
- If the record is already loaded or when you would be using the result, use present?.
- In Rails 4.2 and below, there is not much of a use case for any? if you understand when to use exists? or present? . However, for newer versions of Rails, it is generally recommended to use any?
-
Rule of thumb:
- In Rails 4.2 and below, negate the exists? if you want to check for existence. For newer versions, you can use .empty?
- As same with present?, use blank? only if the record is already loaded.
-
Count vs Size vs Length
- length
- Much like present?, loads the association into memory if not yet loaded.
- count
- Will always generate a COUNT SQL query. Similar behavior with exists?.
- size
- Like any?, behaves like length if the array is already loaded. Otherwise, it defers to using a COUNT query.
-
Rule of thumb:
- It is generally recommended to use size.
- If the record is already loaded or when you need to load the association in memory, use length.
Tune your Puma config
Puma is the default web server for Rails applications. Puma is a single-worker process that serves multiple requests.
You can tune Puma to run in clustered mode, with multiple workers and multiple threads. Each app development is unique, so load test in a production environment to determine the optimal number of workers and threads.
Most people recommend 1 worker per CPU core. This is a good starting point but not a rule. It is possible to increase the number of CPU cores by three to four and still see significant performance improvements.
This would, however, increase RAM or CPU, so make sure you load-test it. Rails apps are known for consuming a lot of memory, so ensure you dont run out.
There is no one size fits all, but you should aim to have at most 3 workers on each server or container with 3 to five threads.
You can move to a more powerful instance of dyno if you are unable to have 3 workers due to CPU or memory limitations. This great podcast will help you tune your web server configuration. Also, take a look at Puma v5s experimental features! It was a great tool that I found very useful.
Use a fast JSON serializer.
I would stay away from using the still popular active_model_serializer gem. Use the fast_json_api serializer, originally from Netflix or something similar.
You can also use the oj gem to speed up json processing. This can make a big difference when processing large amounts of JSON.
A CDN should be used in front of the load balancer.
AWS users should point their Route53 records to a CloudFront Distribution. This distribution will be in front of the Application Load Balancing (ALB) if they are using AWS.
Cloudfront will optimize traffic to your application through the closest edge location within the AWS global network. There is a good chance that an edge location is closer to the user than ALB. You may experience different results, but I experienced an 80-100ms improvement in latency by simply pointing my DNS to Cloudfront.
This then points to ALB. Please note that I have not yet implemented any caching.
Be sure to modify CloudFronts error pages to point to your applications page before you do this. CloudFront errors can be a nuisance to user experience.
You can also set up AWS WAF to protect your app from malicious attacks.
Use HTTP caching
After you have set up a CDN, you can cache your http data through the CDN. Because everything is cached at an edge, this will make it fast.
This is not an easy task, and you need to take into account many factors. If you happen to accidentally cache the wrong items, it can cause headaches. It can pay off, but be careful.
Redis is used for fragment caching
Rails stores data in its file store by default. This is not the best way to go if you have multiple web servers that each have their own cache stores.
Rails 5 has now included support for caching via Redis.
Redis stores the cache within memory, which makes any app load much faster. Your site will be able to serve thousands of customers by identifying the parts of your app that are not changing often.
To make Redis cache content load faster, you might also consider hiring the gem hiredis. ElastiCache can be used in production to help reduce the administrative burden of managing your Redis cluster.
Use soft deleted
Hard deletes are generally not recommended. Hardly deletes the row completely from the database and is permanent.
If you do not have backups, there is no way to "undo them". It would be useful to know when records have been deleted. The popular discard gem is an elegant way of soft-deleting records.
For pagination, switch to Pagy.
Kaminari, will_paginate and other popular pagination gems consume more memory than Pagy. Pagy, the new kid on campus, is more efficient than Pagy.
Use jemmaloc or set MALLOC_ARENA_MAX=2
Check if your apps memory consumption has improved by setting MALLOC_ARENA_MAX=2. This simple change was able to reduce the apps memory footprint by approximately 10%.
Another option is to run ruby with jemmaloc. This has been proven to decrease memory consumption. Although I dont have any experience with running ruby with jemmaloc, there are many articles that recommend it.
Make use of the database whenever you can
Rails developers should be able to use SQL. Active Record is a layer on top of SQL. Instead of using Rubys and. select/methods to select data, you can use Active Record instead.
SQL was designed for querying and filtering data. It is faster and more efficient than Ruby. Use the correct tool skills for the job.
For complex queries, you may have no choice but to use raw SQL by using ActiveRecord.connection.execute(sql_string).
Although raw SQL is faster than ActiveRecord, its still better to use ActiveRecord whenever possible. Long lines of SQL can be very frustrating in the long term. If ActiveRecord is hindering you from completing the task, only use raw SQL.
Rails migrations are dangerous.
Generate only additive migrations and those that are compatible with your current application. Also, when adding indexes, always add a disable_ddl_transaction! Together with the algorithm: concurrently to prevent any unexpected downtimes when deploying to production.
This topic is too long, so be sure to read the guidelines for the strong_migrations gem. Although I dont use the gem often, it is helpful to know the rules for achieving zero downtime migrations.
Know your indexes
It is a good rule to index all foreign keys in your tables. To check if there are missing indexes, you can use the lol_dba gem.
Be familiar with all types of indexes. Although my experience has been primarily with Postgres, I believe it is fairly similar to other SQL engines. Heres a quick overview:
- Multi-column indexes
- Can be significantly faster than a single index so this should be considered. However, Postgres can combine two separate indexes together so performance may vary.
- The order of the index matters. From the postgres docs:...if your workload includes a mix of queries that sometimes involve only column x, sometimes only column y, and sometimes both columns, you might choose to create two separate indexes on x and y, relying on index combination to process the queries that use both columns. You could also create a multicolumn index on (x, y). This index would typically be more efficient than index combination for queries involving both columns, but it would be almost useless for queries involving only y, so it should not be the only index. A combination of the multicolumn index and a separate index on y would serve reasonably well. For queries involving only x, the multicolumn index could be used, though it would be larger and hence slower than an index on x alone.
- Partial Indexes
- An index with a where clause.
- Can be used to enforce a unique partial constraint.
- Example: Just to illustrate, lets say you want to limit the number of active orders per user. If for some reason, you want to enforce one active order per user at a time, you can easily do this by utilizing a partial unique index.
- This type of index is also useful when accessing a table with a particular condition that would make the resulting filtered records significantly smaller.
- Example: You have an orders table with states pending and complete. Over time, orders with a complete state would grow since orders are marked as complete everyday. However, our apps queries would naturally be more interested in finding pending orders today. Since theres a huge disparity between thousands of pending orders and potentially millions of complete orders, a partial index would be beneficial to reduce the size of the index and significantly improve performance.
- GIN Index
- Commonly used to speed up LIKE queries. While I would generally advise against using your database to do full text search types of queries, a GIN index can be very helpful when the situation arises.
Read More: How much would it cost to hire a Ruby on Rails developer?
Default values and unique/null constraint
Be aware of the default values for your database columns. It should be or null. Or should it be 0. null should have an independent meaning than 0, or an empty string value.
It doesnt really matter if it does. It is best to specify a default value as well as a null constraint. This will save you a lot of headaches later.
For booleans, you almost always need to specify a default value as well as a null constraint. There are three options for boolean columns.
True and null.
ElasticSearch is the best search engine for you.
ElasticSearch makes it easy to have a production-ready search engine. To manage the syntax and quirks of elastic search, you can use search kick with the elasticsearch_rails gem.
This is an additional tool skill to manage, so its best to only use this tool if you have complicated search cases. Although it is easy to set up, it adds another layer to your app. To reduce the maintenance burden on the ElasticSearch cluster, you can also use managed services like AWS ElasticSearch.
You can also use the Ransack gem for something simpler. This gem relies on the database to perform the heavy lifting, so it is best used for very simple searches.
Use pattern matching ( LIKE operator) with care. We made a mistake and confused the _eq ransack predicate with _matches. This caused us to wonder why our queries were so slow.
Consider using count estimates
Postgres count estimations are a good option when counting thousands of records. The count estimate query is extremely fast and usually close enough.
Use Explain and Analyze to make sense of your questions.
EXPLAIN and ANALYZE will help you understand how your query works. Unfortunately, ActiveRecord does not have native support, but you can opt to use the active record-explain-analyze gem.
Complex queries can make it difficult to comprehend the EXPLAIN and ANALYZE results. This handy tool is available from depesz. It will help you understand your explanation statements.
Run a few queries to gain database insight
To get a better understanding of your databases current state, there are SQL queries you can run. You can take a look at all the SQL commands available in the rails_pg_extras gem.
You can run a lot of rake tasks to gain insight into your index_usage and locks.
To reduce the bloat of table/indexes, use pg_repack
Are you able to look back on a long-running database? Do you feel that queries are getting more and more slowly? Are you referring to a table with frequent updates and deletions? Run rake: bloat gem to determine if any tables or indexes are experiencing bloat.
Bloat increases when we perform thousands of updates and deletes to our database. Postgres internally does not delete a row when it deletes a record.
It is simply marked as inaccessible to future transactions. This is a simplified explanation, but the row is still visible on the disk. This could grow, especially for large databases.
This is bloat, which slows down your queries.
To remove all bloat, run a vacuum against your table. This command will add an EXCLUSIVE LOCKE to the table. It means that there are no reads and writes for the duration of this command.
Yikes. This could cause production environments to go down. Install pg_repack instead. This tool will allow you to eliminate all bloat and not require an exclusive lock.
Redis is used for fragment caching.
Rails stores data in its file store by default. This is not the best way to go if you have multiple web servers that each have their own cache stores.
Rails 5 has now included support for caching via Redis.
Redis stores the cache within memory, which makes any app load much faster. Your site will be able to serve thousands of customers by identifying the parts of your app that are not changing often.
To make Redis cache content load faster, you might also consider hiring the gem hiredis. ElastiCache can be used in production to help reduce the administrative burden of managing your Redis cluster.
Use soft deleted
Hard deletes are generally not recommended. Hardly deletes all rows from the database and are final. If you do not have backups, there is no way to "undo them".
It would be useful to know when records have been deleted. The popular discard gem is an elegant way of soft-deleting records.
For pagination, switch to Pagy.
Kaminari, will_paginate and other popular pagination gems consume more memory than Pagy. Pagy, the new kid on campus, is more efficient than Pagy.
Want More Information About Our Services? Talk to Our Consultants!
Wrapping up
we have best development company.Thats it! Congratulations for making it to the end! There are certain things I missed, but there wont be a complete list.
We got best web development services for your business. Hire web developers!