Monday, May 3, 2010

SVN: write post-commit hooks using PHP

Linux shell scripting might be difficult for a number of people. Hence, I am writing this to help those who are not familiar with shell scripting.

There are cases when you want to see the changes immediately after committing your work to svn server. There are 2 ways I can think of:
  1. Go to svn server and do a manual svn update. I do not recommend this repetitive task.
  2. Creating a post-commit hook that do the svn update automatically.  
This guide will help you create a post-commit hook using PHP script.

Requirements:
Assumptions:
  • You should have multiple repositories. Unless you have only one project. By having multiple repositories, you can have a different version control for each repository.
Go to your svn repository, in my case it's located under:
/home/svn/projectX
You should see a number of directories under projectX, let's look at hooks and forget the rest. You should see the following files:
  • post-commit.tmpl
  • post-lock.tmpl
  • post-revprop-change.tmpl
  • post-unlock.tmpl
  • pre-commit.tmpl
  • pre-lock.tmpl
  • pre-revprop-change.tmpl
  • pre-unlock.tmpl
  • start-commit.tmpl
For this guide we only interested in post-commit hook. This hook will execute after some body commit their work to svn server. You can easily do the same for all other hooks.

Now, create a new file call post-commit. This file will be executed after svn commit is executed.
#!/usr/bin/php5
<?php

/* Debugging - you never know if this file is being executed! Check the log */
//file_put_contents('/tmp/svn.log',  date("H:i:s"));

/* Location of your web directory */
chdir('/home/web/svn');

/* All projects check out should be here */
$repos = scandir('.'); 
/* Only do svn update if the repository committed is found in the /home/web/svn */
if ($repo = basename($_SERVER['argv'][1]) && in_array(
$repo, $repos)) {
    `/usr/bin/svn update $repo --username username --password password --quiet`;
}
Make sure this file have executable permission and its belong to correct user /group. If you can't make it works, try:
chmod 0777 post-commit
This would allowed everyone to execute post-commit script.

Note: there are 2 arguments passed to the post-commit script:
  1. $_SERVER['argv'][1]  (the path to this repository)
  2. $_SERVER['argv'][2]  (the number of the revision just committed)
     

Thursday, April 1, 2010

Transfer MySQL database to another server with minimal downtime through replication

There are cases when you need to transfer MySQL data from one server to another powerful server. Here are some of the common methods:
  1. Backup/restore MySQL database
    Backup data from the source server and restore on the destination server. This is the safest way to transfer but it will consume some time depending on the amount of data you have. Usually, it's taking much longer for restoring than backing up. Especially, if we are using InnoDB database engine.
  2. Replication MySQL server
    Personally, I prefer this method as the service downtime is minimal. If you don't know MySQL Replication, please read it.
Furthermore, this document will only focus on transferring MySQL data to another server using MySQL Replication.

Objectives:
  • Transfer MySQL data from source server to destination server.
Requirements:
  • Can perform MySQL Replication.
Assumptions:
Assume that you have bin-log enabled on the source (master) server and configurations are configured correctly on both servers. Otherwise, please read my MySQL Replication post.

Procedures:
  1. Disable write on MySQL source server. Temporarily disable write queries on the source server in order for us to do a backup with consistence data. All write queries are stored on log file. Write operation will be resumed once write is enabled.
    mysql> FLUSH TABLES WITH READ LOCK;
  2. Get the binary log position
    mysql> SHOW MASTER STATUS;
    If we see something like this, then we are on the right track:
    +------------------+----------+--------------+------------------+
    | File             | Position | Binlog_Do_DB | Binlog_Ignore_DB |
    +------------------+----------+--------------+------------------+
    | mysql-bin.000001 |      106 |              |                  |
    +------------------+----------+--------------+------------------+
    1 row in set (0.00 sec)
    Please take note of the position (106)! This is the log position where we start backing up the data.
  3. Backup the databases on source server using mysqldump
    mysqldump --routines --triggers --single-transaction --quick -uroot -p --all-databases > all-dbs.sql
  4. Enable write on MySQL source server. This allowed data to be written to MySQL database again.
    mysql> UNLOCK TABLES;
  5. Transfer the database backup file to MySQL destination server.
  6. Restore the database on the destination server
    mysql -uroot -ppassword mysql < /path/to/all-dbs.sql
    Note: -ppassword: no space between -p & password
  7. Set the binary log position
    mysql> CHANGE MASTER TO
    MASTER_HOST='192.168.0.1',
    MASTER_USER='slave_user',
    MASTER_PASSWORD='password',
    MASTER_LOG_FILE='mysql-bin.001',
    MASTER_LOG_POS=106;
  8. Start replication
    mysql> SLAVE START;
  9. Wait until destination (slave) server catch up (synced) with source (master) server.
    1. Stop MySQL service on destination server:
      Debian based:  service mysql restart
      Red Hat based: /etc/init.d/mysqld restart
    2. All database access must now point to the destination server. For minimal downtime, we should configure port-forwarding using iptables on the source server. This mean all incoming traffic from MySQL port (3306) should be forwarded to the destination MySQL server. Setup iptables port-forwarding is beyond the scope of this document.
    3. Now we have to make sure all servers accessing the source MySQL server have to point destination MySQL server. After this is done, we can disable port-forwarding on the source server.
By now, the source server should be able to power off. This method is by far the quickest way I could think of.

Sunday, March 21, 2010

MySQL Replication

Two years ago, I had to learn MySQL replication because we had to transfer our databases to a new server with minimal downtime. If it's performed correctly, the downtime could be under 60 seconds. In my case, the downtime was about 5 minutes because of my carelessness. Nonetheless, MySQL replication can be used for many purposes including:
  1. Scalability - if most of your queries are read.
  2. Data security
  3. Analytics - there are cases when you have complex queries and you do not want to put the stress on the production server.
  4. Long-distance data distribution
Whatever your reason for coming here. Let's cut it short and start configuring.

Configuring the master server
  1. Master server must have binary log enabled
  2. Unique server-id must be configured
Edit my.cnf, for
  • Red Hat based server: /etc/my.cnf
  • Debian based server: /etc/mysql/my.cnf
Insert the following lines under [mysqld]:
[mysqld]
log-bin   = mysql-bin
server-id = 1
Make sure the following lines must be commented out. Otherwise, slave servers won't be able to access the master:
#skip-networking
#bind-address    = 127.0.0.1
Now, restart MySQL service:
Debian based:  service mysql restart
Red Hat based: /etc/init.d/mysqld restart
Create replication user on master server
  1. Log into MySQL server as root
    mysql -uroot -p
  2. Now, on MySQL shell create the replication user
    mysql> GRANT REPLICATION SLAVE ON *.* TO 'slave_user'@'%' IDENTIFIED BY 'password';
    mysql> FLUSH PRIVILEGES;
Configuring the slave server
  1. Unique server-id must be configured. Remember, none of the slave/master servers can have the same server-id. Server-id enabled communication between the master and slave servers.
  2. Enabling binary log is not compulsory but it recommended for data backup and crash recovery. There might be a case when you want this slave server to be the master server for other slaves.
  3. The following configurations can be set/changed whilst MySQL service is running:
    • master-host: master IP address or hostname.
    • master-user: MySQL user account with replication privilege.
    • master-password: replication user password.
    • master-connect-retry: seconds to retry to connect after network failure.
Edit my.cnf and insert the following lines under [mysqld]:
[mysqld]
log-bin               = mysql-bin
server-id             = 2
master-host           = 192.168.0.1
master-user           = slave_user
master-password       = password
master-connect-retry  = 60
Now, restart MySQL service.

Preparing to replicate
Most of the cases, we have our master database running long before the slave. Hence, the master database should have a lot of data. We must import data from master to slave before replicating. There are two ways:
  1. LOAD DATA FROM MASTER:
    use this if you are willing to have the master server being locked during the importing operation.
    1. Log into MySQL server as root:
      mysql -uroot -p
    2. Now, importing and replicating from MySQL shell:
      mysql> LOAD DATA FROM MASTER;
    Done! Life cannot be simpler than this!

    1. Common procedure:

      Tasks on master server

      1. Enabling READ LOCK from master server for mysqldump later. We want to make sure that data will not be changed while we dumping the databases:
        mysql> FLUSH TABLES WITH READ LOCK;
      2. Now, get the log sequence number:
        mysql> SHOW MASTER STATUS;
        If we see something like this, then we are on the right track:
        +------------------+----------+--------------+------------------+
        | File             | Position | Binlog_Do_DB | Binlog_Ignore_DB |
        +------------------+----------+--------------+------------------+
        | mysql-bin.000001 |      106 |              |                  |
        +------------------+----------+--------------+------------------+
        1 row in set (0.00 sec)
        Please take note of the position (106)! This is the log position where we start backing up the data.
      3. Backup up the database from Linux shell:
        mysqldump --routines --triggers --single-transaction --quick -uroot -p --all-databases > all-dbs.sql
      4. Unlock the master database so data can be written:
        mysql> UNLOCK TABLES;
      5. Now, transfer SQL file (all-dbs.sql) to slave server.

      Tasks on slave server

      1. Import data to slave server:
        mysql -uroot -ppassword mysql < /path/to/all-dbs.sql
        Note: -ppassword: no space between -p & password
      2. Stop slave from replicating:
        mysql> SLAVE STOP;
      3. Now, we have to manually set the log sequence position:
        mysql> CHANGE MASTER TO
        MASTER_HOST='192.168.0.1',
        MASTER_USER='slave_user',
        MASTER_PASSWORD='password',
        MASTER_LOG_FILE='mysql-bin.001',
        MASTER_LOG_POS=106;
      4. Finally, start replicating:
        mysql> SLAVE START;
    By now, our slave server should be replicating from the master server.

    Saturday, February 6, 2010

    Using PHP from the command line

    This guide is intended for PHP programmers. I found a number of Linux users asked why don't you do it in Linux shell?
    Well, there are many possible answers:
    • Not many people familiar with Linux shell scripting
    • Don't want to learn another language
    • PHP have a lot of libraries
    • Who knows?
    Requirements:
    • Unix/Linux like Operating System.
    • You must have php-cli.
    This is how you install php-cli:
    • Ubuntu: apt-get install php5-cli
    • Centos: yum install php-cli
    • FreeBSD assuming that you have ports tree installed:
      # cd /usr/ports/lang/php5-cli
      # make config
      # make install
    There are two ways you can run the PHP scripts:
    1. At command line: php your_scripts.php $arg1 $arg2 ...
    2. At the very first line of your php scripts insert this line if you are using Ubuntu, no space:
      #!/usr/bin/php5
      <?php
      If you are using other OS, replace /usr/bin/php5 with your PHP executable file. Execute your script:
      ./path/to/your_scripts.php $arg1 $arg2 ...
    Let's go for a test drive.
    Insert the following lines into a test file:
    cat > test.php
    Paste from command line (ctrl+shift+v):
    #!/usr/bin/php5
    <?php

    foreach($argv as $key => $value){
        echo "\n" . $key . ": " . $value;
    }
    done with ctrl+c.

    Now, you can execute the test.php file using any of the methods above.
    1. php test.php param1 param2
    2. ./test.php param1 param2
      Note: In order to use this method, you must have executable permission on your php script file:
      chmod 0755 test.php
      .
    Expected Output:
    0: test.php
    1: param1
    2: param2
    Remember, argument [0] is always your php script file.

    Resources:
    1. http://php.net/manual/en/features.commandline.php