Before jumping into the broader topic of repository administration, let's further define what a repository is. How does it look? How does it feel? Does it take its tea hot or iced, sweetened, and with lemon? As an administrator, you'll be expected to understand the composition of a repository both from a logical perspective—dealing with how data is represented inside the repository—and from a physical nuts-and-bolts perspective—how a repository looks and acts with respect to non-Subversion tools. The following section covers some of these basic concepts at a very high level.
Conceptually speaking, a Subversion repository is a sequence of directory trees. Each tree is a snapshot of how the files and directories versioned in your repository looked at some point in time. These snapshots are created as a result of client operations, and are called revisions.
Every revision begins life as a transaction tree. When doing a commit, a client builds a Subversion transaction that mirrors their local changes (plus any additional changes that might have been made to the repository since the beginning of the client's commit process), and then instructs the repository to store that tree as the next snapshot in the sequence. If the commit succeeds, the transaction is effectively promoted into a new revision tree, and is assigned a new revision number. If the commit fails for some reason, the transaction is destroyed and the client is informed of the failure.
Updates work in a similar way. The client builds a temporary transaction tree that mirrors the state of the working copy. The repository then compares that transaction tree with the revision tree at the requested revision (usually the most recent, or «youngest» tree), and sends back information that informs the client about what changes are needed to transform their working copy into a replica of that revision tree. After the update completes, the temporary transaction is deleted.
The use of transaction trees is the only way to make permanent changes to a repository's versioned filesystem. However, it's important to understand that the lifetime of a transaction is completely flexible. In the case of updates, transactions are temporary trees that are immediately destroyed. In the case of commits, transactions are transformed into permanent revisions (or removed if the commit fails). In the case of an error or bug, it's possible that a transaction can be accidentally left lying around in the repository (not really affecting anything, but still taking up space).
In theory, someday whole workflow applications might revolve around more fine-grained control of transaction lifetime. It is feasible to imagine a system whereby each transaction slated to become a revision is left in stasis well after the client finishes describing its changes to repository. This would enable each new commit to be reviewed by someone else, perhaps a manager or engineering QA team, who can choose to promote the transaction into a revision, or abort it.
Transactions and revisions in the Subversion repository can have properties attached to them. These properties are generic key-to-value mappings, and are generally used to store information about the tree to which they are attached. The names and values of these properties are stored in the repository's filesystem, along with the rest of your tree data.
Revision and transaction properties are useful for
associating information with a tree that is not strictly
related to the files and directories in that tree—the
kind of information that isn't managed by client working
copies. For example, when a new commit transaction is created
in the repository, Subversion adds a property to that
transaction named svn:date
—a
datestamp representing the time that the transaction was
created. By the time the commit process is finished, and the
transaction is promoted to a permanent revision, the tree has
also been given a property to store the username of the
revision's author (svn:author
) and a
property to store the log message attached to that revision
(svn:log
).
Revision and transaction properties are
unversioned properties—as they
are modified, their previous values are permanently discarded.
Also, while revision trees themselves are immutable, the
properties attached to those trees are not. You can add,
remove, and modify revision properties at any time in the
future. If you commit a new revision and later realize that
you had some misinformation or spelling error in your log
message, you can simply replace the value of the
svn:log
property with a new, corrected log
message.
As of Subversion 1.1, there are two options for storing data in a Subversion repository. One type of repository stores everything in a Berkeley DB database; the other kind stores data in ordinary flat files, using a custom format. Because Subversion developers often refer to a repository as «the (versioned) filesystem», they have adopted the habit of referring to the latter type of repository as FSFS [31] —a versioned filesystem implementation that uses the native OS filesystem to store data.
When a repository is created, an administrator must decide whether it will use Berkeley DB or FSFS. There are advantages and disadvantages to each, which we'll describe in a bit. Neither back-end is more «official» than another, and programs which access the repository are insulated from this implementation detail. Programs have no idea how a repository is storing data; they only see revision and transaction trees through the repository API.
Таблица 5.1, «Repository Data Store Comparison» gives a comparative overview of Berkeley DB and FSFS repositories. The next sections go into detail.
Таблица 5.1. Repository Data Store Comparison
Feature | Berkeley DB | FSFS |
---|---|---|
Sensitivity to interruptions | very; crashes and permission problems can leave the database «wedged», requiring journaled recovery procedures. | quite insensitive. |
Usable from a read-only mount | no | yes |
Platform-independent storage | no | yes |
Usable over network filesystems | no | yes |
Repository size | slightly larger | slightly smaller |
Scalability: number of revision trees | database; no problems | some older native filesystems don't scale well with thousands of entries in a single directory. |
Scalability: directories with many files | slower | faster |
Speed: checking out latest code | faster | slower |
Speed: large commits | slower, but work is spread throughout commit | faster, but finalization delay may cause client timeouts |
Group permissions handling | sensitive to user umask problems; best if accessed by only one user. | works around umask problems |
Code maturity | in use since 2001 | in use since 2004 |
When the initial design phase of Subversion was in progress, the developers decided to use Berkeley DB for a variety of reasons, including its open-source license, transaction support, reliability, performance, API simplicity, thread-safety, support for cursors, and so on.
Berkeley DB provides real transaction support—perhaps its most powerful feature. Multiple processes accessing your Subversion repositories don't have to worry about accidentally clobbering each other's data. The isolation provided by the transaction system is such that for any given operation, the Subversion repository code sees a static view of the database—not a database that is constantly changing at the hand of some other process—and can make decisions based on that view. If the decision made happens to conflict with what another process is doing, the entire operation is rolled back as if it never happened, and Subversion gracefully retries the operation against a new, updated (and yet still static) view of the database.
Another great feature of Berkeley DB is hot backups—the ability to backup the database environment without taking it «offline». We'll discuss how to backup your repository in «Repository Backup», but the benefits of being able to make fully functional copies of your repositories without any downtime should be obvious.
Berkeley DB is also a very reliable database system. Subversion uses Berkeley DB's logging facilities, which means that the database first writes to on-disk log files a description of any modifications it is about to make, and then makes the modification itself. This is to ensure that if anything goes wrong, the database system can back up to a previous checkpoint—a location in the log files known not to be corrupt—and replay transactions until the data is restored to a usable state. See «Managing Disk Space» for more about Berkeley DB log files.
But every rose has its thorn, and so we must note some known limitations of Berkeley DB. First, Berkeley DB environments are not portable. You cannot simply copy a Subversion repository that was created on a Unix system onto a Windows system and expect it to work. While much of the Berkeley DB database format is architecture independent, there are other aspects of the environment that are not. Secondly, Subversion uses Berkeley DB in a way that will not operate on Windows 95/98 systems—if you need to house a repository on a Windows machine, stick with Windows 2000 or Windows XP. Also, you should never keep a Berkeley DB repository on a network share. While Berkeley DB promises to behave correctly on network shares that meet a particular set of specifications, almost no known shares actually meet all those specifications.
Finally, because Berkeley DB is a library linked
directly into Subversion, it's more sensitive to
interruptions than a typical relational database system.
Most SQL systems, for example, have a dedicated server
process that mediates all access to tables. If a program
accessing the database crashes for some reason, the database
daemon notices the lost connection and cleans up any mess
left behind. And because the database daemon is the only
process accessing the tables, applications don't need to
worry about permission conflicts. These things are not the
case with Berkeley DB, however. Subversion (and programs
using Subversion libraries) access the database tables
directly, which means that a program crash can leave the
database in a temporarily inconsistent, inaccessible state.
When this happens, an administrator needs to ask Berkeley DB
to restore to a checkpoint, which is a bit of an annoyance.
Other things can cause a repository to «wedge»
besides crashed processes, such as programs conflicting over
ownership and permissions on the database files. So while a
Berkeley DB repository is quite fast and scalable, it's best
used by a single server process running as one
user—such as Apache's httpd or
svnserve (see Глава 6, Настройка сервера)—rather than accessing it as
many different users via file:///
or
svn+ssh://
URLs. If using a Berkeley DB
repository directly as multiple users, be sure to read «Supporting Multiple Repository Access Methods».
In mid-2004, a second type of repository storage system came into being: one which doesn't use a database at all. An FSFS repository stores a revision tree in a single file, and so all of a repository's revisions can be found in a single subdirectory full of numbered files. Transactions are created in separate subdirectories. When complete, a single transaction file is created and moved to the revisions directory, thus guaranteeing that commits are atomic. And because a revision file is permanent and unchanging, the repository also can be backed up while «hot», just like a Berkeley DB repository.
The revision-file format represents a revision's directory structure, file contents, and deltas against files in other revision trees. Unlike a Berkeley DB database, this storage format is portable across different operating systems and isn't sensitive to CPU architecture. Because there's no journaling or shared-memory files being used, the repository can be safely accessed over a network filesystem and examined in a read-only environment. The lack of database overhead also means that the overall repository size is a bit smaller.
FSFS has different performance characteristics too. When committing a directory with a huge number of files, FSFS uses an O(N) algorithm to append entries, while Berkeley DB uses an O(N^2) algorithm to rewrite the whole directory. On the other hand, FSFS writes the latest version of a file as a delta against an earlier version, which means that checking out the latest tree is a bit slower than fetching the fulltexts stored in a Berkeley DB HEAD revision. FSFS also has a longer delay when finalizing a commit, which could in extreme cases cause clients to time out when waiting for a response.
The most important distinction, however, is FSFS's inability to be «wedged» when something goes wrong. If a process using a Berkeley DB database runs into a permissions problem or suddenly crashes, the database is left unusable until an administrator recovers it. If the same scenarios happen to a process using an FSFS repository, the repository isn't affected at all. At worst, some transaction data is left behind.
The only real argument against FSFS is its relative immaturity compared to Berkeley DB. It hasn't been used or stress-tested nearly as much, and so a lot of these assertions about speed and scalability are just that: assertions, based on good guesses. In theory, it promises a lower barrier to entry for new administrators and is less susceptible to problems. In practice, only time will tell.