1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-13 02:12:41 +01:00

Update some storage documentation for new adjustment workflows

Summary: Ref T1191. General update of this document, which remains mostly accurate. Remove a warning.

Test Plan: Read document.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T1191

Differential Revision: https://secure.phabricator.com/D10760
This commit is contained in:
epriestley 2014-11-01 08:29:37 -07:00
parent f5c426639c
commit 18161d00a0
2 changed files with 135 additions and 104 deletions

View file

@ -4,93 +4,116 @@
This document describes key components of the database schema and should answer This document describes key components of the database schema and should answer
questions like how to store new types of data. questions like how to store new types of data.
= Database System = Database System
===============
Phabricator uses MySQL with InnoDB engine. The only exception is the Phabricator uses MySQL or another MySQL-compatible database (like MariaDB
or Amazon RDS).
Phabricator the InnoDB table engine. The only exception is the
`search_documentfield` table which uses MyISAM because MySQL doesn't support `search_documentfield` table which uses MyISAM because MySQL doesn't support
fulltext search in InnoDB. fulltext search in InnoDB (recent versions do, but we haven't added support
yet).
Let us know if you need to use other database system: @{article:Give Feedback! We are unlikely to ever support other incompatible databases like PostgreSQL or
Get Support!}. SQLite.
= PHP Drivers = PHP Drivers
===========
Phabricator supports [[ http://www.php.net/book.mysql | MySQL ]] and Phabricator supports [[ http://www.php.net/book.mysql | MySQL ]] and
[[ http://www.php.net/book.mysqli | MySQLi ]] PHP extensions. Most installations [[ http://www.php.net/book.mysqli | MySQLi ]] PHP extensions.
use MySQL but MySQLi should work equally well.
= Databases = Databases
=========
Each Phabricator application has its own database. The names are prefixed by Each Phabricator application has its own database. The names are prefixed by
`phabricator_`. This design has two advantages: `phabricator_` (this is configurable). This design has two advantages:
* Each database is easier to comprehend and to maintain. - Each database is easier to comprehend and to maintain.
* We don't do cross-database joins so each database can live on its own machine - We don't do cross-database joins so each database can live on its own
which is useful for load-balancing. machine. This gives us flexibility in sharding data later.
= Connections = Connections
===========
Phabricator specifies if it will use any opened connection just for reading or Phabricator specifies if it will use any opened connection just for reading or
also for writing. This allows opening write connections to master and read also for writing. This allows opening write connections to a primary and read
connections to slave in master/slave replication. It is useful for connections to a replica in primary/replica setups (which are not actually
load-balancing. supported yet).
= Tables = Tables
======
Each table name is prefixed by its application. For example, Differential Most table names are prefixed by their application names. For example,
revisions are stored in database `phabricator_differential` and table Differential revisions are stored in database `phabricator_differential` and
`differential_revision`. This duplicity allows easy recognition of the table in table `differential_revision`. This generally makes queries easier to recognize
DarkConsole (see @{article:Using DarkConsole}) and other places. and understand.
The exception is tables which share the same schema over different databases The exception is a few tables which share the same schema over different
such as `edge`. databases such as `edge`.
We use lower-case table names with words separated by underscores. The reason is We use lower-case table names with words separated by underscores.
that MySQL can be configured (with `lower_case_table_names`) to lower-case the
table names anyway.
= Column Names = Column Names
============
Phabricator uses camelCase names for columns. The main advantage is that they Phabricator uses `camelCase` names for columns. The main advantage is that they
directly map to properties in PHP classes. directly map to properties in PHP classes.
Don't use MySQL reserved words (such as `order`) for column names. Don't use MySQL reserved words (such as `order`) for column names.
= Data Types = Data Types
==========
Phabricator uses `int unsigned` columns for storing dates instead of `date` or Phabricator defines a set of abstract data types (like `uint32`, `epoch`, and
`datetime`. We don't need to care about time-zones in both MySQL and PHP because `phid`) which map to MySQL column types. The mapping depends on the MySQL
of it. The other reason is that PHP internally uses numbers for storing dates. version.
Phabricator uses UTF-8 encoding for storing all text data. We use Phabricator uses `utf8mb4` character sets where available (MySQL 5.5 or newer),
`utf8_general_ci` collation for free-text and `utf8_bin` for identifiers. and `binary` character sets in most other cases. The primary motivation is to
allow 4-byte unicode characters to be stored (the `utf8` character set, which
is more widely available, does not support them). On newer MySQL, we use
`utf8mb4` to take advantage of improved collation rules.
Phabricator stores dates with an `epoch` abstract data type, which maps to
`int unsigned`. Although this makes dates less readable when browsing the
database, it makes date and time manipulation more consistent and
straightforward in the application.
We don't use the `enum` data type because each change to the list of possible We don't use the `enum` data type because each change to the list of possible
values requires altering the table (which is slow with big tables). We use values requires altering the table (which is slow with big tables). We use
numbers (or short strings in some cases) mapped to PHP constants instead. numbers (or short strings in some cases) mapped to PHP constants instead.
= JSON = JSON and Other Serialized Data
==============================
Some data don't require structured access - you don't need to filter or order by Some data don't require structured access -- we don't need to filter or order by
them. We store these data as text fields in JSON format. This approach has them. We store these data as text fields in JSON format. This approach has
several advantages: several advantages:
* If we decide to add another unstructured field then we don't need to alter the - If we decide to add another unstructured field then we don't need to alter
table (which is slow for big tables in MySQL). the table (which is slow for big tables in MySQL).
* Table structure is not cluttered by fields which could be unused most of the - Table structure is not cluttered by fields which could be unused most of the
time. time.
An example of such usage can be found in column An example of such usage can be found in column
`differential_diffproperty.data`. `differential_diffproperty.data`.
= Primary Keys = Primary Keys
============
Most tables have auto-increment column named `id`. However creating such column Most tables have auto-increment column named `id`. Adding an ID column is
is not required for tables which are not usually directly referenced (such as appropriate for most tables (even tables that have another natural unique key),
tables expressing M:N relations). Example of such table is as it improves consistency and makes it easier to perform generic operations
`differential_relationship`. on objects.
= Indexes = For example, @{class:LiskMigrationIterator} allows you to very easily apply a
migration to a table using a constant amount of memory provided the table has
an `id` column.
Indexes
======
Create all indexes necessary for fast query execution in most cases. Don't Create all indexes necessary for fast query execution in most cases. Don't
create indexes which are not used. You can analyze queries @{article:Using create indexes which are not used. You can analyze queries @{article:Using
@ -100,77 +123,90 @@ Older MySQL versions are not able to use indexes for tuple search:
`(a, b) IN ((%s, %d), (%s, %d))`. Use `AND` and `OR` instead: `(a, b) IN ((%s, %d), (%s, %d))`. Use `AND` and `OR` instead:
`((a = %s AND b = %d) OR (a = %s AND b = %d))`. `((a = %s AND b = %d) OR (a = %s AND b = %d))`.
= Foreign Keys = Foreign Keys
============
We don't use InnoDB's foreign keys because our application is so great that We don't use foreign keys because they're complicated and we haven't experienced
no inconsistencies can arise. It will just slow us down. significant issues with data inconsistency that foreign keys could help prevent.
Empirically, we have witnessed first hand as `ON DELETE CASCADE` relationships
accidentally destroy huge amounts of data. We may pursue foreign keys
eventually, but there isn't a strong case for them at the present time.
= PHIDs = PHIDs
=====
Each globally referencable object in Phabricator has its associated PHID Each globally referencable object in Phabricator has its associated PHID
(Phabricator ID) which serves as a global identifier. We use PHIDs for ("Phabricator ID") which serves as a global identifier, similar to a GUID.
referencing data in different databases. We use PHIDs for referencing data in different databases.
We use both autoincrementing IDs and global PHIDs because each is useful in We use both autoincrementing IDs and global PHIDs because each is useful in
different contexts. Autoincrementing IDs are chronologically ordered and allow different contexts. Autoincrementing IDs are meaningfully ordered and allow
us to construct short, human-readable object names (like D2258) and URIs. Global us to construct short, human-readable object names (like `D2258`) and URIs.
PHIDs allow us to represent relationships between different types of objects in Global PHIDs allow us to represent relationships between different types of
a homogeneous way. objects in a homogeneous way.
For example, the concept of "subscribers" is more powerfully done with PHIDs For example, infrastructure like "subscribers" can be implemented easily with
because we could theoretically have users, projects, teams, and more all as PHID relationships: different types of objects (users, projects, mailing lists)
"subscribers" of other objects. Using an ID column we would need to add a are permitted to subscribe to different types of objects (revisions, tasks,
"type" column to avoid ID collision; using PHIDs does not require this etc). Without PHIDs, we would need to add a "type" column to avoid ID collision;
additional column. using PHIDs makes implementing features like this simpler.
= Transactions = Transactions
============
Transactional code should be written using transactions. Example of such code is Transactional code should be written using transactions. Example of such code is
inserting multiple records where one doesn't make sense without the other or inserting multiple records where one doesn't make sense without the other or
selecting data later used for update. See chapter in @{class:LiskDAO}. selecting data later used for update. See chapter in @{class:LiskDAO}.
= Advanced Features = Advanced Features
=================
We don't use MySQL advanced features such as triggers, stored procedures or We don't use MySQL advanced features such as triggers, stored procedures or
events because we like expressing the application logic in PHP more than in SQL. events because we like expressing the application logic in PHP more than in SQL.
Some of these features (especially triggers) can also cause big confusion. Some of these features (especially triggers) can also cause a great deal of
confusion, and are generally more difficult to debug, profile, version control,
update, and understand than application code.
Avoiding these advanced features is also good for supporting other database Schema Denormalization
systems (which we don't support anyway). ======================
= Schema Denormalization = Phabricator uses schema denormalization sparingly. Avoid denormalization unless
there is a compelling reason (usually, performance) to denormalize.
Phabricator uses schema denormalization for performance reasons sparingly. Try Schema Changes and Migrations
to avoid it if possible. =============================
= Changing the Schema = To create a new schema change or migration:
There are three simple steps to update the schema: **Create a database patch**. Database patches go in
`resources/sql/autopatches/`. To change a schema, use a `.sql` file and write
in SQL. To perform a migration, use a `.php` file and write in PHP. Name your
file `YYYYMMDD.patchname.ext`. For example, `20141225.christmas.sql`.
# Create a `.sql` file in `resources/sql/patches/`. This file should: **Keep patches small**. Most schema change statements are not transactional. If
- Contain the appropriate MySQL commands to update the schema. a patch contains several SQL statements and fails partway through, it normally
- Be named as `YYYYMMDD.patchname.ext`. For example, `20130217.example.sql`. can not be rolled back. When a user tries to apply the patch again later, the
- Use `${NAMESPACE}` rather than `phabricator` for database names. first statement (which, for example, adds a column) may fail (because the column
- Use `COLLATE utf8_bin` for any columns that are to be used as identifiers, already exists). This can be avoided by keeping patches small (generally, one
such as PHID columns. Otherwise, use `COLLATE utf8_general_ci`. statement per patch).
- Name all indexes so it is possible to delete them later.
# Edit `src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php` and
add your patch to
@{method@phabricator:PhabricatorBuiltinPatchList::getPatches}.
# Run `bin/storage upgrade`.
It is also possible to create more complex patches in PHP for data migration **Use namespace and character set variables**. When defining a `.sql` patch,
(due to schema changes or otherwise.) However, the schema changes themselves you should use these variables instead of hard-coding namespaces or character
should be done in separate `.sql` files. Order can be guaranteed by editing set names:
`src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php`
appropriately.
See the | Variable | Meaning | Notes |
[[https://secure.phabricator.com/rPb39175342dc5bee0c2246b05fa277e76a7e96ed3 |---|---|---|
| commit adding policy storage for Paste ]] for a reasonable example of the code | {$NAMESPACE} | Storage Namespace | Defaults to `phabricator` |
changes. | {$CHARSET} | Default Charset | Mostly used to specify table charset |
| {$COLLATE_TEXT} | Text Collation | For most text (case-sensitive) |
| {$COLLATE_SORT} | Sort Collation | For sortable text (case-insensitive) |
| {$CHARSET_FULLTEXT} | Fulltext Charset | Specify explicitly for fulltext |
| {$COLLATE_FULLTEXT} | Fulltext Collate | Specify explicitly for fulltext |
= See Also =
* @{class:LiskDAO} **Test your patch**. Run `bin/storage upgrade` to test your patch.
* @{class:PhabricatorPHID}
See Also
========
- @{class:LiskDAO}

View file

@ -148,16 +148,11 @@ final class PhabricatorStorageManagementAdjustWorkflow
pht( pht(
"Found %s issues(s) with schemata, detailed above.\n\n". "Found %s issues(s) with schemata, detailed above.\n\n".
"You can review issues in more detail from the web interface, ". "You can review issues in more detail from the web interface, ".
"in Config > Database Status.\n\n". "in Config > Database Status. To better understand the adjustment ".
"workflow, see \"Managing Storage Adjustments\" in the ".
"documentation.\n\n".
"MySQL needs to copy table data to make some adjustments, so these ". "MySQL needs to copy table data to make some adjustments, so these ".
"migrations may take some time.". "migrations may take some time.",
// TODO: Remove warning once this stabilizes.
"\n\n".
"WARNING: This workflow is new and unstable. If you continue, you ".
"may unrecoverably destory data. Make sure you have a backup before ".
"you proceed.",
new PhutilNumber(count($adjustments)))); new PhutilNumber(count($adjustments))));
$prompt = pht('Fix these schema issues?'); $prompt = pht('Fix these schema issues?');