innodb_ruby 0.9.0 → 0.9.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -29,6 +29,7 @@ class Innodb::Inode
29
29
  # Construct a new Inode by reading an FSEG header from a cursor.
30
30
  def self.new_from_cursor(space, cursor)
31
31
  data = {
32
+ :offset => cursor.position,
32
33
  :fseg_id => cursor.name("fseg_id") {
33
34
  cursor.get_uint64
34
35
  },
@@ -62,6 +63,7 @@ class Innodb::Inode
62
63
  @data = data
63
64
  end
64
65
 
66
+ def offset; @data[:offset]; end
65
67
  def fseg_id; @data[:fseg_id]; end
66
68
  def not_full_n_used; @data[:not_full_n_used]; end
67
69
  def free; @data[:free]; end
@@ -70,6 +72,14 @@ class Innodb::Inode
70
72
  def magic_n; @data[:magic_n]; end
71
73
  def frag_array; @data[:frag_array]; end
72
74
 
75
+ def inspect
76
+ "<%s space=%s, fseg=%i>" % [
77
+ self.class.name,
78
+ space.inspect,
79
+ fseg_id,
80
+ ]
81
+ end
82
+
73
83
  # Helper method to determine if an Inode is in use. Inodes that are not in
74
84
  # use have an fseg_id of 0.
75
85
  def allocated?
@@ -124,7 +134,7 @@ class Innodb::Inode
124
134
 
125
135
  # Compare one Innodb::Inode to another.
126
136
  def ==(other)
127
- fseg_id == other.fseg_id
137
+ fseg_id == other.fseg_id if other
128
138
  end
129
139
 
130
140
  # Dump a summary of this object for debugging purposes.
@@ -109,8 +109,8 @@ class Innodb::List
109
109
  end
110
110
 
111
111
  # Return a list cursor for the list.
112
- def list_cursor(node=nil)
113
- ListCursor.new(self, node)
112
+ def list_cursor(node=:min, direction=:forward)
113
+ ListCursor.new(self, node, direction)
114
114
  end
115
115
 
116
116
  # Return whether the given item is present in the list. This depends on the
@@ -127,36 +127,50 @@ class Innodb::List
127
127
  return enum_for(:each)
128
128
  end
129
129
 
130
- c = list_cursor
131
- while e = c.next
132
- yield e
130
+ list_cursor.each_node do |node|
131
+ yield node
133
132
  end
134
133
  end
135
134
 
136
135
  # A list iteration cursor used primarily by the Innodb::List #cursor method
137
136
  # implicitly. Keeps its own state for iterating through lists efficiently.
138
137
  class ListCursor
139
- def initialize(list, node=nil)
140
- @list = list
141
- @cursor = node
138
+ def initialize(list, node=:min, direction=:forward)
139
+ @initial = true
140
+ @list = list
141
+ @direction = direction
142
+
143
+ case node
144
+ when :min
145
+ @node = @list.first
146
+ when :max
147
+ @node = @list.last
148
+ else
149
+ @node = node
150
+ end
142
151
  end
143
152
 
144
- # Reset the list cursor to its default starting state, which will allow
145
- # iteration forwards from the first entry (using #next) or backwards
146
- # from the last entry (using #prev).
147
- def reset
148
- @cursor = nil
153
+ def node
154
+ if @initial
155
+ @initial = false
156
+ return @node
157
+ end
158
+
159
+ case @direction
160
+ when :forward
161
+ next_node
162
+ when :backward
163
+ prev_node
164
+ end
149
165
  end
150
166
 
151
167
  # Return the previous entry from the current position, and advance the
152
168
  # cursor position to the returned entry. If the cursor is currently nil,
153
169
  # return the last entry in the list and adjust the cursor position to
154
170
  # that entry.
155
- def prev
156
- if @cursor
157
- @cursor = @list.prev(@cursor)
158
- else
159
- @cursor = @list.last
171
+ def prev_node
172
+ if node = @list.prev(@node)
173
+ @node = node
160
174
  end
161
175
  end
162
176
 
@@ -164,11 +178,19 @@ class Innodb::List
164
178
  # cursor position to the returned entry. If the cursor is currently nil,
165
179
  # return the first entry in the list and adjust the cursor position to
166
180
  # that entry.
167
- def next
168
- if @cursor
169
- @cursor = @list.next(@cursor)
170
- else
171
- @cursor = @list.first
181
+ def next_node
182
+ if node = @list.next(@node)
183
+ @node = node
184
+ end
185
+ end
186
+
187
+ def each_node
188
+ unless block_given?
189
+ return enum_for(:each_node)
190
+ end
191
+
192
+ while n = node
193
+ yield n
172
194
  end
173
195
  end
174
196
  end
@@ -1,7 +1,6 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
 
3
3
  # An InnoDB transaction log file.
4
-
5
4
  class Innodb::Log
6
5
  # A map of the name and position of the blocks that form the log header.
7
6
  LOG_HEADER_BLOCK_MAP = {
@@ -14,32 +13,39 @@ class Innodb::Log
14
13
  # Number of blocks in the log file header.
15
14
  LOG_HEADER_BLOCKS = LOG_HEADER_BLOCK_MAP.size
16
15
 
16
+ # The size in bytes of the log file header.
17
+ LOG_HEADER_SIZE = LOG_HEADER_BLOCKS * Innodb::LogBlock::BLOCK_SIZE
18
+
17
19
  # Maximum number of log group checkpoints.
18
20
  LOG_CHECKPOINT_GROUPS = 32
19
21
 
20
22
  # Open a log file.
21
- def initialize(file)
22
- @file = File.open(file)
23
+ def initialize(filename)
24
+ @file = File.open(filename)
23
25
  @size = @file.stat.size
24
26
  @blocks = (@size / Innodb::LogBlock::BLOCK_SIZE) - LOG_HEADER_BLOCKS
27
+ @capacity = @blocks * Innodb::LogBlock::BLOCK_SIZE
25
28
  end
26
29
 
27
30
  # The size (in bytes) of the log.
28
31
  attr_reader :size
29
32
 
30
- # The number of blocks in the the log.
33
+ # The log capacity (in bytes).
34
+ attr_reader :capacity
35
+
36
+ # The number of blocks in the log.
31
37
  attr_reader :blocks
32
38
 
33
39
  # Get the raw byte buffer for a specific block by block offset.
34
40
  def block_data(offset)
35
41
  raise "Invalid block offset" unless (offset % Innodb::LogBlock::BLOCK_SIZE).zero?
36
- @file.seek(offset)
37
- @file.read(Innodb::LogBlock::BLOCK_SIZE)
42
+ @file.sysseek(offset)
43
+ @file.sysread(Innodb::LogBlock::BLOCK_SIZE)
38
44
  end
39
45
 
40
46
  # Get a cursor to a block in a given offset of the log.
41
47
  def block_cursor(offset)
42
- Innodb::Cursor.new(block_data(offset), 0)
48
+ BufferCursor.new(block_data(offset), 0)
43
49
  end
44
50
 
45
51
  # Return the log header.
@@ -49,7 +55,8 @@ class Innodb::Log
49
55
  {
50
56
  :group_id => c.name("group_id") { c.get_uint32 },
51
57
  :start_lsn => c.name("start_lsn") { c.get_uint64 },
52
- :created_by => c.name("created_by") { c.seek(16).get_bytes(4) }
58
+ :file_no => c.name("file_no") { c.get_uint32 },
59
+ :created_by => c.name("created_by") { c.get_string(32) }
53
60
  }
54
61
  end
55
62
  end
@@ -62,7 +69,7 @@ class Innodb::Log
62
69
  {
63
70
  :number => c.name("number") { c.get_uint64 },
64
71
  :lsn => c.name("lsn") { c.get_uint64 },
65
- :offset => c.name("offset") { c.get_uint32 },
72
+ :lsn_offset => c.name("lsn_offset") { c.get_uint32 },
66
73
  :buffer_size => c.name("buffer_size") { c.get_uint32 },
67
74
  :archived_lsn => c.name("archived_lsn") { c.get_uint64 },
68
75
  :group_array =>
@@ -88,10 +95,14 @@ class Innodb::Log
88
95
  @checkpoint ||=
89
96
  {
90
97
  :checkpoint_1 => block_cursor(offset1).name("checkpoint_1") do |cursor|
91
- read_checkpoint(cursor)
98
+ cp = read_checkpoint(cursor)
99
+ cp.delete(:group_array)
100
+ cp
92
101
  end,
93
102
  :checkpoint_2 => block_cursor(offset2).name("checkpoint_2") do |cursor|
94
- read_checkpoint(cursor)
103
+ cp = read_checkpoint(cursor)
104
+ cp.delete(:group_array)
105
+ cp
95
106
  end
96
107
  }
97
108
  end
@@ -1,8 +1,5 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
 
3
- require "innodb/cursor"
4
- require "pp"
5
-
6
3
  # An InnoDB transaction log block.
7
4
  class Innodb::LogBlock
8
5
  # Log blocks are fixed-length at 512 bytes in InnoDB.
@@ -11,9 +8,21 @@ class Innodb::LogBlock
11
8
  # Offset of the header within the log block.
12
9
  HEADER_OFFSET = 0
13
10
 
11
+ # The size of the block header.
12
+ HEADER_SIZE = 4 + 2 + 2 + 4
13
+
14
14
  # Offset of the trailer within ths log block.
15
15
  TRAILER_OFFSET = BLOCK_SIZE - 4
16
16
 
17
+ # The size of the block trailer.
18
+ TRAILER_SIZE = 4
19
+
20
+ # Offset of the start of data in the block.
21
+ DATA_OFFSET = HEADER_SIZE
22
+
23
+ # Size of the space available for log records.
24
+ DATA_SIZE = BLOCK_SIZE - HEADER_SIZE - TRAILER_SIZE
25
+
17
26
  # Mask used to get the flush bit in the header.
18
27
  HEADER_FLUSH_BIT_MASK = 0x80000000
19
28
 
@@ -21,15 +30,15 @@ class Innodb::LogBlock
21
30
  # log block contents.
22
31
  def initialize(buffer)
23
32
  unless buffer.size == BLOCK_SIZE
24
- raise "Log block buffer provided was not #{BLOCK_SIZE} bytes"
33
+ raise "Log block buffer provided was not #{BLOCK_SIZE} bytes"
25
34
  end
26
35
 
27
36
  @buffer = buffer
28
37
  end
29
38
 
30
- # Return an Innodb::Cursor object positioned at a specific offset.
39
+ # Return an BufferCursor object positioned at a specific offset.
31
40
  def cursor(offset)
32
- Innodb::Cursor.new(@buffer, offset)
41
+ BufferCursor.new(@buffer, offset)
33
42
  end
34
43
 
35
44
  # Return the log block header.
@@ -49,6 +58,22 @@ class Innodb::LogBlock
49
58
  end
50
59
  end
51
60
 
61
+ # Return a slice of actual block data (that is, excluding header and
62
+ # trailer) starting at the given offset.
63
+ def data(offset = DATA_OFFSET)
64
+ length = header[:data_length]
65
+
66
+ if length == BLOCK_SIZE
67
+ length -= TRAILER_SIZE
68
+ end
69
+
70
+ if offset < DATA_OFFSET || offset > length
71
+ raise "Invalid block data offset"
72
+ end
73
+
74
+ @buffer.slice(offset, length - offset)
75
+ end
76
+
52
77
  # Return the log block trailer.
53
78
  def trailer
54
79
  @trailer ||= cursor(TRAILER_OFFSET).name("trailer") do |c|
@@ -58,79 +83,28 @@ class Innodb::LogBlock
58
83
  end
59
84
  end
60
85
 
61
- # The constants used by InnoDB for identifying different log record types.
62
- RECORD_TYPES = {
63
- 1 => :MLOG_1BYTE,
64
- 2 => :MLOG_2BYTE,
65
- 4 => :MLOG_4BYTE,
66
- 8 => :MLOG_8BYTE,
67
- 9 => :REC_INSERT,
68
- 10 => :REC_CLUST_DELETE_MARK,
69
- 11 => :REC_SEC_DELETE_MARK,
70
- 13 => :REC_UPDATE_IN_PLACE,
71
- 14 => :REC_DELETE,
72
- 15 => :LIST_END_DELETE,
73
- 16 => :LIST_START_DELETE,
74
- 17 => :LIST_END_COPY_CREATED,
75
- 18 => :PAGE_REORGANIZE,
76
- 19 => :PAGE_CREATE,
77
- 20 => :UNDO_INSERT,
78
- 21 => :UNDO_ERASE_END,
79
- 22 => :UNDO_INIT,
80
- 23 => :UNDO_HDR_DISCARD,
81
- 24 => :UNDO_HDR_REUSE,
82
- 25 => :UNDO_HDR_CREATE,
83
- 26 => :REC_MIN_MARK,
84
- 27 => :IBUF_BITMAP_INIT,
85
- 28 => :LSN,
86
- 29 => :INIT_FILE_PAGE,
87
- 30 => :WRITE_STRING,
88
- 31 => :MULTI_REC_END,
89
- 32 => :DUMMY_RECORD,
90
- 33 => :FILE_CREATE,
91
- 34 => :FILE_RENAME,
92
- 35 => :FILE_DELETE,
93
- 36 => :COMP_REC_MIN_MARK,
94
- 37 => :COMP_PAGE_CREATE,
95
- 38 => :COMP_REC_INSERT,
96
- 39 => :COMP_REC_CLUST_DELETE_MARK,
97
- 40 => :COMP_REC_SEC_DELETE_MARK,
98
- 41 => :COMP_REC_UPDATE_IN_PLACE,
99
- 42 => :COMP_REC_DELETE,
100
- 43 => :COMP_LIST_END_DELETE,
101
- 44 => :COMP_LIST_START_DELETE,
102
- 45 => :COMP_LIST_END_COPY_CREATE,
103
- 46 => :COMP_PAGE_REORGANIZE,
104
- 47 => :FILE_CREATE2,
105
- 48 => :ZIP_WRITE_NODE_PTR,
106
- 49 => :ZIP_WRITE_BLOB_PTR,
107
- 50 => :ZIP_WRITE_HEADER,
108
- 51 => :ZIP_PAGE_COMPRESS,
109
- }
110
-
111
- SINGLE_RECORD_MASK = 0x80
112
- RECORD_TYPE_MASK = 0x7f
113
-
114
- # Return a preamble of the first record in this block.
115
- def first_record_preamble
116
- return nil unless header[:first_rec_group] > 0
117
- cursor(header[:first_rec_group]).name("header") do |c|
118
- type_and_flag = c.name("type") { c.get_uint8 }
119
- type = type_and_flag & RECORD_TYPE_MASK
120
- type = RECORD_TYPES[type] || type
121
- single_record = (type_and_flag & SINGLE_RECORD_MASK) > 0
122
- case type
123
- when :MULTI_REC_END, :DUMMY_RECORD
124
- { :type => type }
125
- else
126
- {
127
- :type => type,
128
- :single_record => single_record,
129
- :space => c.name("space") { c.get_ic_uint32 },
130
- :page_number => c.name("page_number") { c.get_ic_uint32 },
131
- }
132
- end
86
+ # A helper function to return the checksum from the trailer, for
87
+ # easier access.
88
+ def checksum
89
+ trailer[:checksum]
90
+ end
91
+
92
+ # Calculate the checksum of the block using InnoDB's log block
93
+ # checksum algorithm.
94
+ def calculate_checksum
95
+ cksum = 1
96
+ shift = (0..24).cycle
97
+ cursor(0).each_byte_as_uint8(TRAILER_OFFSET) do |b|
98
+ cksum &= 0x7fffffff
99
+ cksum += b + (b << shift.next)
133
100
  end
101
+ cksum
102
+ end
103
+
104
+ # Is the block corrupt? Calculate the checksum of the block and compare to
105
+ # the stored checksum; return true or false.
106
+ def corrupt?
107
+ checksum != calculate_checksum
134
108
  end
135
109
 
136
110
  # Dump the contents of a log block for debugging purposes.
@@ -142,9 +116,5 @@ class Innodb::LogBlock
142
116
  puts
143
117
  puts "trailer:"
144
118
  pp trailer
145
-
146
- puts
147
- puts "record:"
148
- pp first_record_preamble
149
119
  end
150
120
  end
@@ -1,79 +1,84 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
 
3
+ # Group of InnoDB logs files that make up the redo log.
3
4
  class Innodb::LogGroup
4
- def initialize
5
- @files = []
5
+
6
+ # Initialize group given a set of sorted log files.
7
+ def initialize(log_files)
8
+ @logs = log_files.map { |fn| Innodb::Log.new(fn) }
9
+ sizes = @logs.map { |log| log.size }
10
+ raise "Log file sizes do not match" unless sizes.uniq.size == 1
6
11
  end
7
12
 
8
- def add_log(file)
9
- if file = Innodb::Log.new(file)
10
- @files.push file
11
- else
12
- raise "Couldn't open #{file}"
13
+ # Iterate through all logs.
14
+ def each_log
15
+ unless block_given?
16
+ return enum_for(:each_log)
17
+ end
18
+
19
+ @logs.each do |log|
20
+ yield log
13
21
  end
14
22
  end
15
23
 
24
+ # Iterate through all blocks.
16
25
  def each_block
17
- @files.each do |file|
18
- file.each_block do |block_number, block|
19
- yield block_number, block
26
+ unless block_given?
27
+ return enum_for(:each_block)
28
+ end
29
+
30
+ each_log do |log|
31
+ log.each_block do |block_index, block|
32
+ yield block_index, block
20
33
  end
21
34
  end
22
35
  end
23
36
 
24
- def current_tail_position
25
- max = 0
26
- max_file = nil
27
- max_block = nil
37
+ # The number of log files in the group.
38
+ def logs
39
+ @logs.count
40
+ end
28
41
 
29
- @files.each_with_index do |file, file_number|
30
- file.each_block do |block_number, block|
31
- if block.header[:block] > max
32
- max = block.header[:block]
33
- max_file = file_number
34
- max_block = block_number
35
- end
36
- end
37
- end
42
+ # Returns the log at the given position in the log group.
43
+ def log(log_no)
44
+ @logs.at(log_no)
45
+ end
38
46
 
39
- { :file => max_file, :block => max_block }
47
+ # The size in byes of each and every log in the group.
48
+ def log_size
49
+ @logs.first.size
40
50
  end
41
51
 
42
- def successor_position(position)
43
- if position[:block] == @files[position[:file]].blocks
44
- if position[:file] == @files.size
45
- { :file => 0, :block => 0 }
46
- else
47
- { :file => position[:file] + 1, :block => 0 }
48
- end
49
- else
50
- { :file => position[:file], :block => position[:block] + 1 }
51
- end
52
+ # The size of the log group (in bytes)
53
+ def size
54
+ @logs.first.size * @logs.count
52
55
  end
53
56
 
54
- def block(file_number, block_number)
55
- @files[file_number].block(block_number)
57
+ # The log group capacity (in bytes).
58
+ def capacity
59
+ @logs.first.capacity * @logs.count
56
60
  end
57
61
 
58
- def block_if_newer(old_block, new_block)
59
- return new_block if old_block.nil?
60
- #puts "old: #{old_block.header[:block]} new: #{new_block.header[:block]}"
61
- if new_block.header[:block] >= old_block.header[:block]
62
- new_block
63
- end
62
+ # Returns the LSN coordinates of the data at the start of the log group.
63
+ def start_lsn
64
+ [@logs.first.header[:start_lsn], Innodb::Log::LOG_HEADER_SIZE]
64
65
  end
65
66
 
66
- def tail_blocks
67
- position = current_tail_position
68
- current_block = nil
69
- while true
70
- until block_if_newer(current_block, new_block = block(position[:file], position[:block]))
71
- #puts "Waiting at the tail: #{position[:file]} #{position[:block]}"
72
- sleep 0.1
73
- end
74
- yield new_block
75
- position = successor_position(position)
76
- current_block = new_block
77
- end
67
+ # Returns the LSN coordinates of the most recent (highest) checkpoint.
68
+ def max_checkpoint_lsn
69
+ checkpoint = @logs.first.checkpoint.max_by{|f,v| v[:number]}.last
70
+ checkpoint.values_at(:lsn, :lsn_offset)
71
+ end
72
+
73
+ # Returns a LogReader using the given LSN reference coordinates.
74
+ def reader(lsn_coord = start_lsn)
75
+ lsn_no, lsn_offset = lsn_coord
76
+ lsn = Innodb::LSN.new(lsn_no, lsn_offset)
77
+ Innodb::LogReader.new(lsn, self)
78
+ end
79
+
80
+ # Parse and return a record at a given LSN.
81
+ def record(lsn_no)
82
+ reader.seek(lsn_no).record
78
83
  end
79
84
  end