PostgreSQL indexing in Rails
rny.io
rny.io
Postgresql (and even Mysql for that matter) lets you explicitly declare foreign key references when you create/alter a table and then the database will enforce integrity of those references for you. Which is great because when someone writes some kind of code outside of Rails to work with your db, and that code has bugs, and those bugs impact data integrity, there is a hard stop at the DB layer preventing dangling/invalid references that will blow something up later.
A side effect of creating these explicit foreign key relationships is that any needed indices are created on both sides of the key. [UPDATE: Nope, I'm wrong, see below.]
Here the author has a Rails setup that fails to declare foreign key constraints when it's making a table with relations. You can see this in the DB description of the `products` table which references the `categories` tables via a plain int called `category_id`. As a result of not having an explicit foreign key constraint this table also has no index on `category_id`.
So given a table with poor data integrity at the SQL/db level and lacking an index as a sympton of this problem, the author treats the symptom and advises the creation of an index on `category_id`, leaving the real problem woefully intact. And that real problem, to be clear, is the fact that the database is not being used properly but rather treated as a relatively dumb place to dump columnar data; things that Postgres gives you for free are set aside and pseudo-duplicated in Rails code.
Now I'm not blasting the author because I don't know if this is a tactical issue or an issue with Rails itself. Does Rails not allow foreign key constrains in the migrations?
Whether this is a flaw in Rails or how it's being used, this is a bad solution to the problem. The RDBMS is your friend, use it. (And if you're not going to use the integrity constraints of the DB why are you using Postgres instead of say BerkeleyDB or a loosely configured MySQL or Mongo or whatever?)
This is simply not true.
Foreign key relationships typically use the primary key of the referenced table, which is implicitly indexed, by virtue of its being a primary key, not its being a foreign key. The referring column is never implicitly indexed, though it often should be explicitly indexed.
rosser=> create table referenced (id int primary key, blah text);
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "referenced_pkey" for table "referenced"
CREATE TABLE
rosser=> create table referencing (id int primary key, referenced_id int references referenced(id), stuff text);
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "referencing_pkey" for table "referencing"
CREATE TABLE
rosser=> \d referenced
Table "public.referenced"
Column | Type | Modifiers
--------+---------+-----------
id | integer | not null
blah | text |
Indexes:
"referenced_pkey" PRIMARY KEY, btree (id)
Referenced by:
TABLE "referencing" CONSTRAINT "referencing_referenced_id_fkey" FOREIGN KEY (referenced_id) REFERENCES referenced(id)
rosser=> \d referencing
Table "public.referencing"
Column | Type | Modifiers
---------------+---------+-----------
id | integer | not null
referenced_id | integer |
stuff | text |
Indexes:
"referencing_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
"referencing_referenced_id_fkey" FOREIGN KEY (referenced_id) REFERENCES referenced(id)I'm really, really unsure about how to use postgres in such a way: most posts that I read are around these really complex setups with zero downtime or these Postgresql-quickstart guides (which I'm not looking for) or Heroku howtos.
Does anyone have a non-devops set of Postgres best practices that could be used to get your transactional MVP up and running on Softlayer ?
1. Install Postgres 9.3, the version after the replication fix.
2. Tune Postgres until it reaches a reasonable speed (lots of tutorials available, primarily just assigning it more RAM)
3. Create a second instance, do a backup, restore backup on second instance
4. Turn on streaming replication, ensure WALs are being received by second instance
5. If you're paranoid, use synchronous replication, which won't finish a transaction until it's been committed on the primary and a streaming secondary.
I'm not our DBA, but I believe this covers the primary setup. You will have to test whether Postgres correctly moves into a new history chain on migration as support for this via WALSender is pretty new I believe.
For example, in FreeBSD, you can set the parameters at runtime via sysctl, but unless you also modify /etc/sysctl.conf, they won't be applied after a reboot.
It's not fun scrambling to figure out what your kernel parameters were after you've applied a security update and rebooted your machine. Not fun at all.
Test out reboots.
A caveat, though: be careful with synchronous replication; it actually increases your chances of having an outage. If your slave goes down with sync rep, the master will no longer accept writes. (To mitigate that risk, have multiple slaves. But that can further increase the latency sync rep introduces, as now the master has to wait for all the slaves to report back a successful write.)
Still, Postgres in a hot standby configuration is relatively simple, still high performance and an actual real database that doesn't need tens of grand in licensing. You really can't go much wrong with it.
The authentication defaults for postgres and mysql are vastly different - I am always tempted to move all authentication to md5 (pretty much the same as mysql). Am I doing it wrong ?
And, no, you aren't doing it wrong. You generally want to use MD5 auth.
In 9.3 at some point this is slated for inclusion in the WALSender. This allows you to do full streaming replication without the requirement for a cluster filesystem to hold your binary logs.
What did hackers (not whole development + ops) teams do before 9.3.2 ? with all due respect, is this the reason why startups still default to mysql over postgres ?
> with all due respect, is this the reason why startups still default to mysql over postgres ?
I do not these particular issues. But if we go back before streaming replication (when people had to transfer WAL files with rsync) then I am pretty sure this was part of the reason for why small companies chose MySQL over PostgreSQL:
The issue I had was that, while the partial index syntax worked and actually did create it, it wasn't reflected anywhere in the generated schema. So I was worried that future developers wouldn't realize there was a partial index (we use schema.rb as the canonical reference of DB state), and if we ever get rid of migrations by condensing them into one big one we might miss that there should be a partial index on the column.
And changing the config to generate schema.sql instead of schema.rb didn't help. It's broken with the latest version of postgresql...
def up
execute('ALTER TABLE people ADD INDEX ...')
end
The one thing to keep in mind though is that if you use ruby schema format, your tests won't pick up those execute statements. In that case it's best to either use the sql format or re-run migrations in the test environment to set up the test db. execute %{
create index index_users_on_name
on users (name)
where state = 'NY';
}Postgres already requires the pointed to relation to have a unique index:
=> create table foo (id int); -- No unique index here.
CREATE TABLE
=> create index foo_id on foo(id);
CREATE INDEX
=> create table bar(foo int references foo(id));
ERROR: there is no unique constraint matching given keys for referenced table "foo"This seems to tie the database closely to the ORM. What if there are multiple applications using the schema? Wouldn't it be better to have the database code & controlling system separate?
I've found it more convenient to have a set of SQL files for specifying tables & indexes and to have the app not control the database schema directly (Perhaps I just don't see the clear advantage, or perhaps Rails tooling is so good it obviates my concerns. :-) ).
When doing a new deploy you shouldn't usually go through the migrations but just load the schema.
In config/application.rb:
config.active_record.schema_format = :sql
Now I am reading book High Performance MySQL: Optimization, Backups, and Replication (yes, I know this post about PostgreSQL) and there are so much stuff to know, so this ORM thing seems like a toy.
So what is wrong to creating schema with plain old (database specific) SQL?
The usual syntax for creating these migration files is in Ruby, as this makes them database-independent, arguably more easily readable, and in newer versions of Rails, reversible.
Of course, you can put plain old SQL in these migrations if you want to as well.
[1]: https://github.com/rails/rails/commit/2d33796457b139a58539c8...
Yes, they're not a hard requirement. However, be aware that any field with a foreign key constraint will be searched during a DELETE that affects the related table, to check RESTRICT DELETE or CASCADE DELETE constraints. It can be very beneficial to performance to have an index on that field for those internal database searches.
The same is true for an UPDATE that affects the PK, but that's relatively rare if you're using surrogate primary keys.