Tuesday, April 2, 2013

Bean Validation (JSR-303) API

Java EE 6 standardının yayınlanalı 3 yılı aşkın bir süre oldu. Bu uzunca bir süre. Hatta yakında yeni sürümünü bekliyoruz. Ancak buna rağmen henüz daha yeni yeni kullanılmaya başlandığını görüyoruz. Java EE 6 kurumsal Java uygulaması geliştirmeyi kolaylaştıran epey bir yenilik içeriyor. Bunlardan biri alan nesnelerini doğrulamakta kullanılan "Bean Validation" Uygulama Programlama Arayüzüdür (UPA). 
Java EE platformu çok kullanıcılı, ölçeklenebilir ve her zaman erişilebilinir yazılım mimarisi üzerinde dağıtılmış, bileşen tabanlı ve Java SE 5 ile gelen damgalar (=annotations) aracılığı ile betimsel yazılım geliştirme modeli sunuyor. Bu yaklaşım kurumsal uygulama geliştirmeyi oldukça kolaylaştırıyor. Java EE standardı makinamıza indirip kurabileceğimiz bir yazılım değildir. Bir dizi arayüz şartnamesini tek bir şemsiye altında toplayan bir ana çatıdır. Toplam 33 adet bu tür şartnameden oluşmaktadır. Bean Validation UPA'sı da bunlardan sadece biridir ve platforma bu sürüm ile yeni eklenmiştir. Java EE uygulamaları çalışabilmeleri için bir uygulama sunucusuna ihtiyaç duyarlar. Uygulama sunucuları işte bu 33 standardı gerçekleyen yazılımlardır ve makinamıza bunlardan birini kuruyor olmamız gerekir. Uygulamamızı uygulama sunucusuna yayınlarız ve uygulama sunucusu bileşenlerden oluşan uygulamamızı çalıştırır. Bileşenler de tıpkı sınıflar gibi tanımlanır. Arada ufak bir fark bulunmaktadır. Nesneye dayalı programlamada modelleme için kullandığımızdan sınıflardan problemi çözebilmek için nesneler yaratmamız gerekir. Yarattığımız bu nesneler Java'da bellekte Heap adını verdiğimiz bölgeye yerleşir ve biz ona herhangi bir istekte bulunmadığımız sürece hiç bir iş yapmaz. Bu anlamda nesnelerin pasif olduklarını söyleyebiliriz. Bileşenler ise yine sınıf olarak tasarlanmakta ve tanımlanmaktadırlar. Ancak biz hiçbir zaman bu bileşenlerden nesne yaratmayız ya da metodlarını çağırmayız. Bileşenler var olmak ve yaşayabilmek için bir kap teknolojisine ihtiyaç duyarlar. Kap gerektiğinde bileşen yaratmaya karar verir, gerektiğinde metodlarından birini çağırır ve bazen de bileşeni yok eder. Yani bileşenlerin yaşam döngüsü kap tarafından yönetilir. Java EE'de bu bileşenler ölçeklenebilirlik ve her zaman erişilebilirlik için dağıtık bir yapıda yer alabilirler. Java EE'de iki tane kap bulunmaktadır: Web Kabı ve EJB Kabı. Uygulama sunucusunun bu iki kaptan oluştuğunu söyleyebiliriz. 
Uygulama içinde farklı türde bileşenler bulunur. Sunum katmanında Servlet/JSP iş mantığı katmanında ise "Session Bean" ve "Message Driven Bean" olarak adlandırılan bileşen modelleri vardır. Her bir tür bileşenin bir sorumluluğu ve uzmanlığı vardır. Örneğin JSP uygulamanın kullanıcıya dönük yüzünü oluşturmaktan sorumludur. İş mantığını ise basit karmaşıklıkta olanlarını Session Bean ve uzun soluklu ya da yığın işlemleri Message Driven Bean bileşenlerine yaptırıyoruz.
Geliştirici olarak doğrulama sorumluluğunu her katmanda yerine getirmeliyiz. Kullancıdan bilgileri alırken doğruluğunu sınamalıyız. İş katmanında iş mantığını çalıştırmadan önce ve tümleştirme katmanında alan nesnelerini veritabanında kalıcı hale getirmeden önce geçerlilik sınaması yapmamız gerekir. Özet olarak geçerlilik sınaması tek bir katmanın bir sorumluluğu değildir. Sınamayı yapabilmek için bir dizi kurala ihtiyacımız olacaktır. Bu kuralları tanımlamak için en uygun yer neresi olabilir? İşte bu soruya JSR-303 alan nesneleri olarak cevap veriyor. Her standardın, standarda dönüşmeden önce geçmesi gereken belirli prosedürler bulunmaktadır. Java'nın gelişimi Java Community Process tarafından yönetilmektedir. Yenilik veya değişimleri nedenleri ile açıklayan bir doküman JCP'ye sunulur. Kabul edilen her başvuru için bir JSR açılır, şartname lideri atanır. Lider uzmanlardan oluşan teknik bir grup oluşturur. Bu grup şartname dokümanı yazmaktan ve bu şartname ile uyumlu bir referans uygulama geliştirmekten sorumludur. Çeşitli aşamalardan geçen şartname en sonunda JCP tarafından oylanır ve kabul edilirse Java şartnamesine dönüşür. Bu sürecin de bir JSR şartnamesi mevcuttur: JSR-355. 
Kuralları damgalar aracılığı ile alan nesnelerinde tanımlıyoruz. Standard damgalar fazla değil: @Min, @Max, @NotNull, @Null, @Past, @Pattern, @Size. Ancak genişletilebilinir. Biz de kendi damgalarımızı ve bu damganın iliştirildiği özniteliği doğrulayacak kodu yazabiliriz. Bunu basit bir örnekle göstermek istiyorum. Örneğimize kredi kartı için bir damga tasarlayarak başlayacağız: 
1:  package com.example.validation;  
2:    
3:  import java.lang.annotation.ElementType;  
4:  import java.lang.annotation.Retention;  
5:  import java.lang.annotation.RetentionPolicy;  
6:  import java.lang.annotation.Target;  
7:  import javax.validation.Constraint;  
8:  import javax.validation.Payload;  
9:    
10:  /**  
11:   *  
12:   * @author Binnur Kurt  
13:   */  
14:  @Constraint(validatedBy={CreditCardValidator.class})  
15:  @Retention(RetentionPolicy.RUNTIME)  
16:  @Target(ElementType.FIELD)  
17:  public @interface CreditCard {  
18:      String delimeter() default "-";  
19:      public String message() default "This is not a valid credit card!";  
20:      public Class<?>[] groups() default {};  
21:      public Class<? extends Payload>[] payload() default {};  
22:  }  
Ancak damgalar birer meta-datadır. Derlendiklerinde bytecode'a dönüşmezler. Eğer @Retention(RetentionPolicy.RUNTIME) damgası ile tanımlanmışlarsa .class dosyasına yazılır ve sınıf yükleyicisi ile de PermGen'e yerleştirilirler. Böylelikle de Reflection API aracılığı ile uygulama tarafından işlenebilirler. Şimdi bu damgalar ile tanımlanmış bir özniteliğin içeriğini doğrulayacak bir kod yazmamız gerekiyor:
com.example.validation.CreditCardValidator.java:
1:  package com.example.validation;  
2:    
3:  import com.example.domain.CreditCardBean;  
4:  import javax.validation.ConstraintValidator;  
5:  import javax.validation.ConstraintValidatorContext;  
6:    
7:  /**  
8:   *  
9:   * @author Binnur Kurt  
10:   */  
11:  public class CreditCardValidator implements ConstraintValidator<CreditCard, CreditCardBean> {  
12:    
13:    private String delimeter;  
14:    
15:    @Override  
16:    public void initialize(CreditCard constraintAnnotation) {  
17:      delimeter = constraintAnnotation.delimeter();  
18:    }  
19:    
20:    @Override  
21:    public boolean isValid(CreditCardBean value, ConstraintValidatorContext context) {  
22:      int sum = 0;  
23:      for (int i = 0; i < 4; i++) {  
24:        String num = value.getCardNumbers(i);  
25:        sum += (num.charAt(1)-'0') + (num.charAt(3)-'0');  
26:      }  
27:      for (int i = 0; i < 4; i++) {  
28:        String num = value.getCardNumbers(i);  
29:        sum += 2 * ((num.charAt(0)-'0') + (num.charAt(2)-'0'));  
30:      }  
31:    
32:      if (sum % 10 == 0) {  
33:        return true;  
34:      }  
35:      return false;  
36:    }  
37:  }  
com.example.domain.CreditCardBean.java:
1:  package com.example.domain;  
2:    
3:  import java.io.Serializable;  
4:    
5:  /**  
6:   *  
7:   * @author Binnur Kurt  
8:   */  
9:  public class CreditCardBean implements Serializable {  
10:    
11:    private String[] cardNumbers;  
12:    
13:    public CreditCardBean() {  
14:      cardNumbers= new String[4];  
15:    }  
16:    
17:    public String[] getCardNumbers() {  
18:      return cardNumbers;  
19:    }  
20:      
21:    public String getCardNumbers(int index) {  
22:      return cardNumbers[index];  
23:    }  
24:    
25:    public void setCardNumbers(String[] cardNumbers) {  
26:      this.cardNumbers = cardNumbers;  
27:    }  
28:  }  
Damga ile doğrulamayı yapacak kod arasındaki ilişki damganın validatedBy isimli özniteliğini n değerine sınıfın adı verilerek sağlanır: 
@Constraint(validatedBy={CreditCardValidator.class})
Şimdi artık @CreditCard damgasını herhangi bir alan sınıfında kullanabiliriz. Bunun için JSF 2'deki bir CDI Bean'i seçtim:
com.example.beans.UserBean.java:
1:  package com.example.beans;  
2:  import com.example.domain.CreditCardBean;  
3:  import com.example.validation.CreditCard;  
4:  import java.io.Serializable;  
5:  import javax.enterprise.context.SessionScoped;  
6:  import javax.inject.Named;  
7:  import javax.validation.constraints.Pattern;  
8:  import javax.validation.constraints.Size;  
9:  /**  
10:   *  
11:   * @author Binnur Kurt  
12:   */  
13:  @Named("user")  
14:  @SessionScoped  
15:  public class UserBean implements Serializable{  
16:    @Size(min=5)  
17:    private String username;  
18:    @Pattern(regexp="^\\+?[a-z0-9](([-+.]|[_]+)?[a-z0-9]+)*@([a-z0-9]+(\\.|\\-))+[a-z]{2,6}$")  
19:    private String email;  
20:    @CreditCard  
21:    private CreditCardBean creditCard;  
22:    public UserBean() {  
23:    }  
24:    public CreditCardBean getCreditCard() {  
25:      return creditCard;  
26:    }  
27:    public void setCreditCard(CreditCardBean creditCard) {  
28:      this.creditCard = creditCard;  
29:    }  
30:    public String getEmail() {  
31:      return email;  
32:    }  
33:    public void setEmail(String email) {  
34:      this.email = email;  
35:    }  
36:    public String getUsername() {  
37:      return username;  
38:    }  
39:    public void setUsername(String username) {  
40:      this.username = username;  
41:    }  
42:  }  
JSR-303'de yeni kısıtlar oluşturmanın iki yolu bulunuyor. Bunlardan ilkinde aslında yeni bir kısıt tanımlamış olmuyoruz. Sadece standart ile gelen kısıtın kullanımına yeni bir isim veriyoruz:
Email.java:
package com.example.validation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;
import javax.validation.constraints.Pattern;

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Pattern(regexp="^\\+?[a-z0-9](([-+.]|[_]+)?[a-z0-9]+)*@([a-z0-9]+(\\.|\\-))+[a-z]{2,6}$",message="You must enter a valid e-mail!")
@Constraint(validatedBy={})
public @interface Email {
    String message() default "You must enter a valid e-mail!";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}
StrongPassword.java:

package com.example.validation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;
import javax.validation.constraints.Pattern;

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Pattern.List({
  @Pattern(regexp="^.*\\d+.*$",message="Your password must contain a digit!"),
  @Pattern(regexp="^.*[-_]+.*$",message="Your password must contain \"-\" or \"_\"")
})
@Constraint(validatedBy = {})
public @interface StrongPassword {
    String message() default "{validation.strongPassword1}";
     Class<?>[] groups() default {};
     Class<? extends Payload>[] payload() default {};
}

İkinci yöntemde ise doğrulamasını da bizim yaptığımız yepyeni bir kısıt tanımlıyoruz:
Iban.java:

package com.example.validation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;

@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy=IbanValidator.class)
public @interface Iban {
 String message() default "{validation.iban}";

 Class<?>[] groups() default {};

 Class<? extends Payload>[] payload() default {};

}
IbanValidator.java:

package com.example.validation;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class IbanValidator implements 
     ConstraintValidator<Iban, String>{
 private static final long MAX = 999999999;
 private static final long MODULUS = 97;

 @Override
 public void initialize(Iban iban) {
 }

 @Override
 public boolean isValid(String value, ConstraintValidatorContext ctx) {
  if (value == null || value.length() < 5) {
   return false;
  }
  try {
   int modulusResult = calculateModulus(value);
   return (modulusResult == 1);
  } catch (Exception ex) {
   return false;
  }
 }

 private int calculateModulus(String code) throws Exception {
  String reformattedCode = code.substring(4) + code.substring(0, 4);
  long total = 0;
  for (int i = 0; i < reformattedCode.length(); i++) {
   int charValue = Character
     .getNumericValue(reformattedCode.charAt(i));
   if (charValue < 0 || charValue > 35) {
    throw new Exception("Invalid Character[" + i + "] = '"
      + charValue + "'");
   }
   total = (charValue > 9 ? total * 100 : total * 10) + charValue;
   if (total > MAX) {
    total = (total % MODULUS);
   }
  }
  return (int) (total % MODULUS);
 }

}
TcKimlikNo.java:

package com.example.validation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;

@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy=TcKimlikNoValidator.class)
public @interface TcKimlikNo {
 String message() default "{validation.identityNo}";

 Class<?>[] groups() default {};

 Class<? extends Payload>[] payload() default {};
}
TcKimlikNoValidator.java:

package com.example.validation;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class TcKimlikNoValidator implements 
        ConstraintValidator<TcKimlikNo, String>{

 @Override
 public void initialize(TcKimlikNo tcKimlikNo) {
 }

 @Override
 public boolean isValid(String value, ConstraintValidatorContext context) {
  if (value==null) return false;
  if (!value.matches("^\\d{11}$")) {
   return false;
  }
  int[] digits = new int[11];
  for (int i = 0; i < digits.length; ++i) {
   digits[i] = value.charAt(i) - '0';
  }
  int x = digits[0];
  int y = digits[1];
  for (int i = 1; i < 5; i++) {
   x += digits[2 * i];
  }
  for (int i = 2; i <= 4; i++) {
   y += digits[2 * i - 1];
  }
  int c1 = 7 * x - y;
  if (c1 % 10 != digits[9]) {
   return false;
  }
  int c2 = 0;
  for (int i = 0; i < 10; ++i) {
   c2 += digits[i];
  }
  if (c2 % 10 != digits[10]) {
   return false;
  }
  return true;
 }

}
Projenin tamamını NetBeans projesi olarak bu adresten indirebilirsiniz. Projede RichFaces 4 kullanıldığı için bağımlı olduğu jar dosyalara ihtiyacınız olacaktır. İlgili dosyaları bu adresten indirebilirsiniz.

Tuesday, March 19, 2013

MySQL 5.6'da Çok Ustalı Yineleme (=Multi-Master Replication)

Yineleme yeteneği MySQL'in 5.0 sürümünden itibaren sahip olduğu bir yetenektir. 5.6 sürümünde yamaklarda birden  fazla iş parçası tanımlamak mümkün olabilmektedir. Böylelikle usta ile aradaki gecikme düşük değerlerde tutulabilinir. 5.6 öncesinde yamaklarda iki adet iş parçacığı vardı:
  1. G/Ç iş parçası: Ustanın binlog kayıtlarını okumaktan ve yerel diske yazmaktan sorumludur.
  2. SQL iş parçasıG/Ç iş parçasının yerel diske yazdığı SQL cümlelerini okuyup çalıştırmaktan sorumludur. 
Artık SQL cümlelerini çalıştırmak için birden fazla iş parçası tanımlayabiliyoruz. Çok çekirdekli sistemlerde çalışan MySQL sunucusu için başarımın artacağı anlamına gelmektedir. Bunun için iş parçası sayısını slave_parallel_workers değişkenine atamak yeterli olmaktadır. Aksi belirtilmez ise varsayılan değeri sıfırdır. Bu durumda 5.6 öncesinde olduğu gibi birer iş parçası çalışır. Makinadaki çekirdek sayısı kadar değer vermek uygun olur:

mysql> set global slave_parallel_workers=4;
Query OK, 0 rows affected (0.00 sec)

mysql> show variables like 'slave_parallel_workers';
+------------------------+-------+
| Variable_name          | Value |
+------------------------+-------+
| slave_parallel_workers | 4     |
+------------------------+-------+
1 row in set (0.00 sec) 

MySQL'de çok ustalı yineleme yapmak mümkündür. Ancak çok ustalı yineleme ile ilgili birkaç problemin olduğunu bilmenizde fayda var. Öncelikle çok ustalı yinelemenin nasıl kurulacağını göstereceğim. Ardından karşılaşılan problemleri ve olası çözümlerine değineceğim. Çok ustalı yinelemede ikiden fazla düğüm kurmak anlamlı değildir. Bir halka yapısında oluşturulacak bu mimaride düğümlerden herhangi biri erişilemez olursa yineleme kesintiye uğrar.

İki düğümlü çok ustalı yineleme için öncelikli olarak sunucuların yapılandırma dosyalarında bir iki tanımlama yapmak gerekir. IP adresleri 192.168.1.66 (Sunucu 1) ve 192.168.1.67 (Sunucu 2) olan iki makinamız bulunsun. Sunucu 1 için yapılandırma dosyasında aşağıdaki tanımlamalar bulunmalıdır:
[mysqld]
log_bin=masterlog
log-slave-updates
server_id = 1
...
Sunucu 2 için yapılandırma dosyasında aşağıdaki tanımlamalar bulunmalıdır:
[mysqld]
log_bin=masterlog
log-slave-updates
server_id = 2
...

Bir numaralı sunucuda yinelemede kullanılacak kullanıcı oluşturulur:

mysql> grant replication slave on *.* to 'repuser'@'192.168.1.%' identified by 'secret';
Query OK, 0 rows affected (0.02 sec)

mysql> show global variables like 'server_id';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| server_id     | 1     |
+---------------+-------+
1 row in set (0.00 sec)

mysql> show binary logs;
+------------------+-----------+
| Log_name         | File_size |
+------------------+-----------+
| masterlog.000001 |       473 |
+------------------+-----------+
1 row in set (0.00 sec)

mysql> flush logs;
Query OK, 0 rows affected (0.14 sec)

mysql> show binary logs;
+------------------+-----------+
| Log_name         | File_size |
+------------------+-----------+
| masterlog.000001 |       520 |
| masterlog.000002 |       120 |
+------------------+-----------+
2 rows in set (0.00 sec)

mysql> change master to
    -> master_host='192.168.1.67',
    -> master_user='repuser',
    -> master_password='secret',
    -> master_log_file='masterlog.000002',
    -> master_log_pos=120,
    -> master_port=3306;
Query OK, 0 rows affected, 2 warnings (0.30 sec)

mysql> start slave;
Query OK, 0 rows affected (0.04 sec)

mysql> show slave status \G
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 192.168.1.67
                  Master_User: repuser
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: masterlog.000002
          Read_Master_Log_Pos: 120
               Relay_Log_File: relaylog.000002
                Relay_Log_Pos: 283
        Relay_Master_Log_File: masterlog.000002
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
              Replicate_Do_DB:
          Replicate_Ignore_DB:
           Replicate_Do_Table:
       Replicate_Ignore_Table:
      Replicate_Wild_Do_Table:
  Replicate_Wild_Ignore_Table:
                   Last_Errno: 0
                   Last_Error:
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 120
              Relay_Log_Space: 456
              Until_Condition: None
               Until_Log_File:
                Until_Log_Pos: 0
           Master_SSL_Allowed: No
           Master_SSL_CA_File:
           Master_SSL_CA_Path:
              Master_SSL_Cert:
            Master_SSL_Cipher:
               Master_SSL_Key:
        Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error:
               Last_SQL_Errno: 0
               Last_SQL_Error:
  Replicate_Ignore_Server_Ids:
             Master_Server_Id: 2
                  Master_UUID: 55458b84-9063-11e2-ab0e-00ff10207b07
             Master_Info_File: C:\opt\mysql-5.6.10\data\master.info
                    SQL_Delay: 0
          SQL_Remaining_Delay: NULL
      Slave_SQL_Running_State: Slave has read all relay log; waiting for the slave I/O thread to update it
           Master_Retry_Count: 86400
                  Master_Bind:
      Last_IO_Error_Timestamp:
     Last_SQL_Error_Timestamp:
               Master_SSL_Crl:
           Master_SSL_Crlpath:
           Retrieved_Gtid_Set:
            Executed_Gtid_Set:
                Auto_Position: 0
1 row in set (0.00 sec)

İki numaralı sunucuda (192.168.1.67) yinelemede kullanılacak kullanıcı oluşturulur:

mysql> grant replication slave on *.* to 'repuser'@'192.168.1.%' identified by 'secret';

Query OK, 0 rows affected (0.02 sec)

mysql> show global variables like 'server_id';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| server_id     | 2     |
+---------------+-------+
1 row in set (0.00 sec)

mysql> show binary logs;
+------------------+-----------+
| Log_name         | File_size |
+------------------+-----------+
| masterlog.000001 |       473 |
+------------------+-----------+
1 row in set (0.00 sec)

mysql> flush logs;
Query OK, 0 rows affected (0.14 sec)

mysql> show binary logs;
+------------------+-----------+
| Log_name         | File_size |
+------------------+-----------+
| masterlog.000001 |       520 |
| masterlog.000002 |       120 |
+------------------+-----------+
2 rows in set (0.00 sec)

mysql> change master to
    -> master_host='192.168.1.66',
    -> master_user='repuser',
    -> master_password='secret',
    -> master_port=3306,
    -> master_log_file='masterlog.000002',
    -> master_log_pos=120;
Query OK, 0 rows affected, 2 warnings (0.30 sec)

mysql> start slave;

Query OK, 0 rows affected (0.05 sec)

mysql> show slave status \G
*************************** 1. row ***************************
               Slave_IO_State: Connecting to master
                  Master_Host: 192.168.1.66
                  Master_User: repuser
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: masterlog.000002
          Read_Master_Log_Pos: 120
               Relay_Log_File: aa-PC-relay-bin.000001
                Relay_Log_Pos: 4
        Relay_Master_Log_File: masterlog.000002
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
              Replicate_Do_DB:
          Replicate_Ignore_DB:
           Replicate_Do_Table:
       Replicate_Ignore_Table:
      Replicate_Wild_Do_Table:
  Replicate_Wild_Ignore_Table:
                   Last_Errno: 0
                   Last_Error:
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 120
              Relay_Log_Space: 120
              Until_Condition: None
               Until_Log_File:
                Until_Log_Pos: 0
           Master_SSL_Allowed: No

           Master_SSL_CA_File:

           Master_SSL_CA_Path:

              Master_SSL_Cert:

            Master_SSL_Cipher:

               Master_SSL_Key:

        Seconds_Behind_Master: NULL

Master_SSL_Verify_Server_Cert: No

                Last_IO_Errno: 

               Last_SQL_Errno: 0
               Last_SQL_Error:
  Replicate_Ignore_Server_Ids:
             Master_Server_Id: 0
                  Master_UUID:
             Master_Info_File: C:\opt\mysql-5.6.10\data\master.info
                    SQL_Delay: 0
          SQL_Remaining_Delay: NULL
      Slave_SQL_Running_State: Slave has read all relay log; waiting for the slave I/O thread to update it
           Master_Retry_Count: 86400
                  Master_Bind:
      Last_IO_Error_Timestamp: 130319 09:34:00
     Last_SQL_Error_Timestamp:
               Master_SSL_Crl:
           Master_SSL_Crlpath:
           Retrieved_Gtid_Set:
            Executed_Gtid_Set:
                Auto_Position: 0
1 row in set (0.00 sec)

Çok ustalı yinelemede temel problem CAP teoremi ile açıklanabilir. Eğer tek başına çalışan bir sunucudaki yazılımı bilgisayar ağı ile biri birine bağlı dağıtılmış bir sisteme dönüştürüyorsanız eskiden olduğu gibi davranmasını beklememelisiniz. Genelde yapılan hatalardan biri de testlerin sadece tek başına bir sistemde yapılmasıdır. Tek başına çalışan sistemde düzgün çalışan bir uygulama dağıtık bir sistemde istenilen biçimde çalışmayabilir. Brewer'in CAP teorimi de bununla ilgilidir. Teorem ile ilgili detayları bu bağlantıdan okuyabilirsiniz. Özetle teorem dağıtık bir sistemde aşağıdaki üç özelliğin tümünün birden sağlanamayacağını söyler: 
  1. Tutarlılık
  2. Her zaman erişilebilirlik
  3. Bölünme bağışıklığı
Bu özelliklerden birinden ödün vermeniz gerekir. Değişik veritabanı ürünleri için CAP teoremine uygun olarak bir sınıflandırma yapan çalışmaya bakmanızda bir fayda var. MySQL çok ustalı yineleme tutarlıktan ödün veriyor. Şimdi tutarlık ile ilgili oluşabilecek problemlere bir kaç tane örnek vereceğim:

Birinci problem:
Sunucu 1 Sunucu 2
mysql> use test;
Database changed


mysql> create table t1 ( 
    -> id int not null auto_increment,
    -> value varchar(30),
    -> primary key(id) 
    -> ) engine=innodb ;
Query OK, 0 rows affected (0.44 sec)

mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)

mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into t1 values (NULL,'jack');
Query OK, 1 row affected (0.00 sec)


mysql> insert into t1 values(NULL,'jack');
Query OK, 1 row affected (0.01 sec)


mysql> commit;
Query OK, 0 rows affected (0.03 sec)

mysql> commit;
Query OK, 0 rows affected (0.03 sec)


2013-02-20 13:07:38 1956 [ERROR] Slave SQL: Error 'Duplicate entry '1' for key 'PRIMARY'' on query. Default database: 'test'. Query: 'insert into t1 values(NULL,'jack')', Error_code: 1062
2013-02-20 13:07:38 1956 [Warning] Slave: Duplicate entry '1' for key 'PRIMARY' Error_code: 1062 
2013-02-20 13:07:38 1956 [ERROR] Error running query, slave SQL thread aborted. Fix the problem, and restart the slave SQL thread with "SLAVE START". We stopped at log 'masterlog.000003' position 1687973

İkinci problem: Bu problem 'lost update' problemi olarak bilinir. Sunucularda aynı veritabanın tablolarında aynı kimlikli kayıtları üzerinde işlem yapılırken,iki farklı istemci kayıtlarda kilit olmadığı için bu kaydı değiştirmeye çalışabilir. Böyle bir durumunda kaydı en son değiştiren (commit gönderen) kazanır. 
Sunucu 1Sunucu 2
mysql> use test;
Database changed

mysql> create table t2 ( 
    -> id int not null auto_increment,
    -> value varchar(30),
    -> unique key(value),
    -> primary key(id) 
    -> ) engine=innodb ;
Query OK, 0 rows affected (0.44 sec)

mysql> insert into t2 values (1,'jack');
Query OK, 1 row affected (0.00 sec)
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)


















mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update t2 set value='jack bauer' where id=1;
Query OK, 1 row affected (0.00 sec)


mysql> update t2 set value='jack shephard' where id=1;
Query OK, 1 row affected (0.00 sec)



mysql> commit;
Query OK, 0 rows affected (0.03 sec)

mysql> commit;
Query OK, 0 rows affected (0.03 sec)

Üçüncü problem
Sunucu 1Sunucu 2
mysql> use test;
Database changed

mysql> create table t2 ( 
    -> id int not null auto_increment,
    -> value varchar(30),
    -> unique key(value),
    -> primary key(id) 
    -> ) engine=innodb ;
Query OK, 0 rows affected (0.44 sec)

mysql> insert into t2 values (1,'jackb'),(2,'jacks');
Query OK, 1 row affected (0.00 sec)
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)


















mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update t2 set value='jack bauer' where id=1;
Query OK, 1 row affected (0.00 sec)


mysql> update t2 set value='jack bauer' where id=2;
Query OK, 1 row affected (0.00 sec)



mysql> commit;
Query OK, 0 rows affected (0.03 sec)

mysql> commit;
Query OK, 0 rows affected (0.03 sec)
Olası çözümler

  1. auto_increment_offset tanımlaması
  2. Çok Ustalı Yineleme Yöneticisi kullanımı
  3. MySQL 5.5 ile beraber gelen yarı-eş zamanlı yineleme 
  4. MySQL 5.6 ile gelen atomik yineleme 

MySQL 5.6'da Bölümleme

Uzunca bir süredir beklediğimiz MySQL 5.6 sürümü nihayet yayınlandı. Bu sürüm Oracle'ın satın almadan sonra çıkardığı ikinci sürüm olma özelliğini taşıyor. Yeni sürüm birçok alanda iyileştirmeler ve yenilikler içeriyor. Bu sürümde de İyileştirmelerin ve yeniliklerin ağırlıklı olarak InnoDB depolama motoru üzerinde gerçekleştirildiğini rahatlıkla söyleyebiliriz. Yeni sürümü bu adresten indirebilirsiniz.
5.6 ile gelen önemli yeniliklerden biri bölümleme ile ilgili. Hem başarımı iyileştirilmiş hem de yönetimini kolaylaştırılmış. Örneğin artık belirli bir bölümü seçip üzerinde SELECT, INSERT, UPDATE ve DELETE cümlelleri çalıştırabiliyoruz. Bir bölümü başka bir tablo ile değiş tokuş edebiliyoruz. Bunlara ilişkin örnekleri aşağıda bulabilirsiniz.

mysql> create table employees (
    -> id int not null auto_increment primary key,
    -> first_name varchar(30),
    -> last_name varchar(30),
    -> salary float(8,2) )
    -> partition by hash(id) partitions 5;     


1 row in set (0.00 sec)

mysql> show table status like 'employees'\G

*************************** 1. row ***************************
           Name: employees
         Engine: InnoDB
        Version: 10
     Row_format: Compact
           Rows: 5
 Avg_row_length: 16384
    Data_length: 81920
Max_data_length: 0
   Index_length: 0
      Data_free: 0
 Auto_increment: 1
    Create_time: NULL
    Update_time: NULL
     Check_time: NULL
      Collation: latin1_swedish_ci
       Checksum: NULL
 Create_options: partitioned
        Comment:
1 row in set (0.00 sec)

mysql> show create table employees\G

*************************** 1. row ***************************
       Table: employees
Create Table: CREATE TABLE `employees` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `first_name` varchar(30) DEFAULT NULL,
  `last_name` varchar(30) DEFAULT NULL,
  `salary` float(8,2) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
/*!50100 PARTITION BY HASH (id)
PARTITIONS 5 */
1 row in set (0.00 sec)

mysql> select partition_name,table_rows from information_schema.partitions where table_name='employees';

+----------------+------------+
| partition_name | table_rows |
+----------------+------------+
| p0             |          0 |
| p1             |          0 |
| p2             |          0 |
| p3             |          0 |
| p4             |          0 |
+----------------+------------+
5 rows in set (0.01 sec)

mysql> insert into employees values
    -> (1,'jack','bauer',24000),
    -> (2,'jack','shephard',32000),
    -> (3,'kate','austen',27500),
    -> (4,'ben','linus',21750),
    -> (5,'claire','littleton',43000),
    -> (6,'jin','kwon',35000),
    -> (7,'sun','kwon',26500),
    -> (8,'john','locke',43500),
    -> (9,'hugo','reyes',125000),
    -> (10,'sayid','jarrah',12500),
    -> (11,'james','ford',33500),
    -> (12,'desmond','hume',25000),
    -> (13,'charlie','pace',45000),
    -> (14,'micheal','dawson',47000),
    -> (15,'juliet','burke',37000);
Query OK, 15 rows affected, 15 warnings (0.07 sec)
Records: 15  Duplicates: 0  Warnings: 15

mysql> select partition_name,table_rows from information_schema.partitions where table_name='employees';
+----------------+------------+
| partition_name | table_rows |
+----------------+------------+
| p0             |          3 |
| p1             |          3 |
| p2             |          3 |
| p3             |          3 |
| p4             |          3 |
+----------------+------------+
5 rows in set (0.01 sec)

mysql> select * from employees partition(p0);
+----+------------+-----------+----------+
| id | first_name | last_name | salary   |




+----+------------+-----------+----------+
|  5 | claire     | littleton | 43000.00 |
| 10 | sayid      | jarrah    | 12500.00 |
| 15 | juliet     | burke     | 37000.00 |
+----+------------+-----------+----------+
3 rows in set (0.00 sec)

mysql> select * from employees partition(p1);
+----+------------+-----------+----------+
| id | first_name | last_name | salary   |
+----+------------+-----------+----------+
|  1 | jack       | bauer     | 24000.00 |
|  6 | jin        | kwon      | 35000.00 |
| 11 | james      | ford      | 33500.00 |
+----+------------+-----------+----------+
3 rows in set (0.00 sec)

mysql> select * from employees partition(p2);
+----+------------+-----------+----------+
| id | first_name | last_name | salary   |
+----+------------+-----------+----------+
|  2 | jack       | shephard  | 32000.00 |
|  7 | sun        | kwon      | 26500.00 |
| 12 | desmond    | hume      | 25000.00 |
+----+------------+-----------+----------+
3 rows in set (0.00 sec)

mysql> select * from employees partition(p3);
+----+------------+-----------+----------+
| id | first_name | last_name | salary   |
+----+------------+-----------+----------+
|  3 | kate       | austen    | 27500.00 |
|  8 | john       | locke     | 43500.00 |
| 13 | charlie    | pace      | 45000.00 |
+----+------------+-----------+----------+
3 rows in set (0.00 sec)

mysql> select * from employees partition(p4);
+----+------------+-----------+-----------+
| id | first_name | last_name | salary    |
+----+------------+-----------+-----------+
|  4 | ben        | linus     |  21750.00 |
|  9 | hugo       | reyes     | 125000.00 |
| 14 | micheal    | dawson    |  47000.00 |
+----+------------+-----------+-----------+
3 rows in set (0.00 sec)


mysql> select * from employees partition(p1,p3);
+----+------------+-----------+----------+
| id | first_name | last_name | salary   |
+----+------------+-----------+----------+
|  1 | jack       | bauer     | 24000.00 |
|  6 | jin        | kwon      | 35000.00 |
| 11 | james      | ford      | 33500.00 |
|  3 | kate       | austen    | 27500.00 |
|  8 | john       | locke     | 43500.00 |
| 13 | charlie    | pace      | 45000.00 |
+----+------------+-----------+----------+
6 rows in set (0.00 sec)


mysql> insert into employees partition(p1) values (NULL,'shannon','rutherford',75000);
Query OK, 1 row affected (0.03 sec)

mysql> select * from employees partition(p1);
+----+------------+------------+----------+
| id | first_name | last_name  | salary   |
+----+------------+------------+----------+
|  1 | jack       | bauer      | 24000.00 |
|  6 | jin        | kwon       | 35000.00 |
| 11 | james      | ford       | 33500.00 |
| 16 | shannon    | rutherford | 75000.00 |
+----+------------+------------+----------+
4 rows in set (0.00 sec)

mysql> insert into employees partition(p2) values (NULL,'ana','cortez',51250);
Query OK, 1 row affected (0.06 sec)

mysql> select * from employees partition(p2);
+----+------------+-----------+----------+
| id | first_name | last_name | salary   |
+----+------------+-----------+----------+
|  2 | jack       | shephard  | 32000.00 |
|  7 | sun        | kwon      | 26500.00 |
| 12 | desmond    | hume      | 25000.00 |
| 17 | ana        | cortez    | 51250.00 |
+----+------------+-----------+----------+
4 rows in set (0.00 sec)



mysql> create table emp_backup like employees;
Query OK, 0 rows affected (1.61 sec)



mysql> alter table emp_backup remove partitioning;
Query OK, 0 rows affected (1.52 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> alter table employees exchange partition p0 with table emp_backup ;


Query OK, 0 rows affected (0.52 sec)

mysql> select * from employees partition (p0);
Empty set (0.00 sec)

mysql> select * from emp_backup;
+----+------------+-----------+----------+
| id | first_name | last_name | salary   |
+----+------------+-----------+----------+
|  5 | claire     | littleton | 43000.00 |
| 10 | sayid      | jarrah    | 12500.00 |
| 15 | juliet     | burke     | 37000.00 |
+----+------------+-----------+----------+
3 rows in set (0.00 sec)