[Rails][MySQL]MySQL5.1のパーティションプラグインの作成 その1

[Rails][MySQL]MySQL5.1のパーティションプラグインの作成 その1


Cassandraは0.8系を待つことにした。ネタがなくなったので、MySQLのパーティショニングプラグインをつくってみる


環境


Windows 7 HomePremium 64bit
ruby 1.8.7 (2011-02-18 patchlevel 334) [i386-mingw32]
mysql Ver 14.14 Distrib 5.1.53, for Win32
rails 2.3.11

基盤作成


適当にRailsアプリを作成したあとに以下を実行
命名規則なんて知りません。。。


ruby script/generate plugin acts_as_mysql_helper

init.rbの記述


location : vendor/plugins/acts_as_mysql_helper/init.rb


require 'acts_as_mysql_helper' #=> lib以下のacts_as_mysql_helper.rbを読み込む

# ActiveRecordにMix-in
ActiveRecord::Base.class_eval do
include ActiveRecord::Acts::MysqlHelper
end

acts_as_mysql_helperの記述


location : vendor/plugins/acts_as_mysql_helper/lib/acts_as_mysql_helper.rb


require 'mysql_helper_base' # base部分(定数とエラークラスだけだが)読み込み
require 'range_partition'# RangePartition操作の実装クラスを読み込む

module ActiveRecord
module Acts

module MysqlHelper
def self.included(base)
base.extend(ActMacro) #ActMacroをぶっこむ
end
end

module ActMacro
# options[:partition_type]で指定したPartitionTypeごとに実装変える予定...
def acts_as_mysql_partition(options = {})
case options[:partition_type]
when nil, "", ActiveRecord::Acts::PartitionDefine::TYPE_RANGE
self.extend(RangePartition)
self.load_partitions # load_partitionsで現在のpartition状態を取り込む
when ActiveRecord::Acts::PartitionDefine::TYPE_LIST
# TODO
return
when ActiveRecord::Acts::PartitionDefine::TYPE_HASH
# TODO
return
when ActiveRecord::Acts::PartitionDefine::TYPE_KEY
# TODO
return
else
raise ActiveRecord::Acts::PartitionTypeError
end
end
end

end
end


とりあえず、Rangeパーティショニングだけ実装してみる


定数とかエラークラスとか


ここらへんのルールがわからないので適当に


location : vendor/plugins/acts_as_mysql_helper/lib/mysql_helper_base.rb


module ActiveRecord
module Acts
class PartitionDefine
TYPE_RANGE = "RANGE"
TYPE_LIST = "LIST"
TYPE_HASH = "HASH"
TYPE_KEY = "KEY"
end

class NotFoundPartition < StandardError
end
end
end

分ける必要ないんじゃないかと思うけどまああとでリファクタリングをするとして置いておく


Rangeパーティションのコア部分を作成する


location : vendor/plugins/acts_as_mysql_helper/lib/range_partition.rb


require 'mysql_helper_base'

module RangePartition
@@partition_type = ActiveRecord::Acts::PartitionDefine::TYPE_RANGE #=> いらない子
@@partitions = [] #=> パーティション情報設定
@@column_is_date = true #=> TO_DAYSするかどうかのフラグ
@@reoganize = false #=> REOGANIZEでパーティション拡張するかどうかのフラグ

# 以下しばらくアクセサ
def reoganize=(value)
@@reoganize = value # true, falseなのにこの実装...
end

def reoganize?
@@reoganize == true
end

def column_is_date=(value)
@@column_is_date = value # true, false..
end

def column_is_date?
@@column_is_date == true
end

def partition_type
@@partition_type
end

def partitions
@@partitions
end

# partition設定を読み込み
# colum_is_dateがtrueだとパーティションのvalue部分を勝手に日付に変換する
# @return [String, String] [0]がパーティション名, [1]がパーティションレンジ
def load_partitions
@@partitions = ( column_is_date? ? load_partitions_as_date : ranges )
end

# primary_keyを変換します
# ActiveRecord::Migrationにもっていくべきだよね
# @param [Array] keys 複合キーとなるカラム名を配列で指定
def change_primary_key(keys)
connection.execute("ALTER TABLE #{table_name} DROP PRIMARY KEY, ADD PRIMARY KEY (#{keys.join(',')})")
end

# パーティションを作成します
# @param [String] key パーティション分割するキーとなるフィールド
# @param [Array] parts {:name, :value}のハッシュを配列に入れたものを指定します
# 命名が適当すぎる
def create_partition(key, parts)
sql = []
parts.each do |f|
sql << "PARTITION #{f[:name]} VALUES LESS THAN (TO_DAYS('#{f[:value].to_s}'))"
end
connection.execute("ALTER TABLE #{table_name} PARTITION BY RANGE (TO_DAYS(#{key})) (#{sql.join(',')});")
end

# パーティションを追加します
# ifが多すぎる気がする
# @param [String] partition_name 追加するパーティション名を指定
# @param [String] value 分割する範囲(less thanのあとに続く数値)を指定
# 例外処理がほとんどない
# column_is_date?がtrueでない場合の処理が適当
# add_date_partitionでvalueをTO_DAYSに変換するSQLが投げられる
def add_partition(partition_name, value)
begin
if column_is_date?
if reoganize?
add_date_partition_with_reorganize(@@partitions[@@partitions.length-1][0], partition_name, value)
else
add_date_partition(partition_name, value)
end
else
add_value_partition(partition_name, value)
end
load_partitions
return true
rescue => e
raise e
end
end

# パーティションを削除します
# @param [String] partition_name 削除するパーティション名を指定します
def drop_partition(partition_name)
raise NotFoundPartition unless contaion_partition?(partition_name)
begin
connection.execute("ALTER TABLE #{table_name} DROP PARTITION #{partition_name}")
load_partitions
return true
rescue => e
raise e
end
end

# 指定されたパーティションが含まれているか確認します
def contaion_partition?(partition_name)
@@partitions.each do |f|
return true if f[0] == partition_name
end
return false
end

# 以下適当SQLいっぱい
protected
def add_date_partition(partition_name, date)
connection.execute("ALTER TABLE #{table_name} ADD PARTITION ( PARTITION #{partition_name} VALUES LESS THAN (TO_DAYS('#{date}')) )")
end

def add_date_partition_with_reorganize(max_value_partition, partition_name, date)
sql <<-SQL
ALTER TABLE #{table_name} REORGANIZE PARTITION #{max_value_partition} INTO
{
PARTITION #{partition_name} VALUES LESS THAN (TO_DAYS('#{date}'))
PARTITION #{max_value_partition} VALUES LESS THAN MAXVALUE
}
SQL
connection.execute(sql)
end

def add_value_partition(partiton_name, value)
connection.execute("ALTER TABLE #{table_name} ADD PARTITION #{partition_name} VALUES LESS THAN (#{value})")
end

def load_partitions_as_date
return ranges.each { |f| f[1] = from_days(f[1]) if f[1].to_i > 0 }
end

# show tablesの結果内のパーティション情報の部分を抜き出して配列に変換
def ranges
return show_create_table.scan(/PARTITION (.+) VALUES LESS THAN \(([0-9]+)\) ENGINE/)
end

def show_create_table
connection.execute("SHOW CREATE TABLE #{table_name}").each {|f| return f[1]}
end

def from_days(value)
connection.execute("SELECT FROM_DAYS(#{value.to_i})").each {|f| return f[0]}
end
end

とりあえずなにも考えずに作成した
reoganizeとかもつけてみたが、試してさえいない
また今度試す。


使い方


サンプルモデル


class RangeModel < ActiveRecord::Base
acts_as_mysql_partition # これ入れる
end

マイグレーションにて


class CreateRangeModels < ActiveRecord::Migration
def self.up
create_table :range_models do |t|
t.string :name
t.timestamps
end

RangeModel.change_primary_key(['id','created_at'])
RangeModel.create_partition('created_at', [{:name => 'p201103', :value => '2011-03-01'}, {:name => 'p201104', :value => '2011-04-01'}])
end

def self.down
drop_table :range_models
end
end

RangeModel.change_primary_key(['key1','key2'...])
=> primary_keyを複合primary_keyに変更できる


RangeModel.create_partition('created_at', [{:name => 'p201103', :value => '2011-03-01'}, {:name => 'p201104', :value => '2011-04-01'}])
=> パーティションを作成する
パーティショニングするカラム名を第1引数に入れる
後ろの配列はパーティション条件を入れる[{:name => 'パーティション名', :value => '日付'}, ...]


現在設定されているパーティションは以下で表示可能


RangeModel.partitions #=> [["p201103", "2011-03-01"], ["p201104", "2011-04-01"]]

一応毎回DBに参照しに行かないようにしているつもりだけどできているのかしら。。。


パーティションを追加


RangeModel.add_partition('p201105', '2011-05-01') #=> true
p RangeModel.partitions #=> [["p201103", "2011-03-01"], ["p201104", "2011-04-01"], ["p201105", "2011-05-01"]]

パーティションを削除


RangeModel.drop_partition('p201105') #=> true
p RangeModel.partitions #=> [["p201103", "2011-03-01"], ["p201104", "2011-04-01"]]

適当に作成したソースコードをコピペしただけー
pluginとか初めてなので、どういう構成にするのが綺麗なのかいろいろ悩んでしまうが
綺麗に作ろうといろいろ考えるのが面白い。
だれかアドバイスくださいー

スポンサーサイト
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。