Class: Innodb::Space

Inherits:
Object
  • Object
show all
Defined in:
lib/innodb/space.rb

Overview

An InnoDB space file, which can be either a multi-table ibdataN file or a single-table “innodb_file_per_table” .ibd file.

Constant Summary collapse

DEFAULT_PAGE_SIZE =

InnoDB’s default page size is 16KiB.

16384
SYSTEM_SPACE_PAGE_MAP =

A map of InnoDB system space fixed-allocation pages. This can be used to check whether a space is a system space or not, as non-system spaces will not match this pattern.

{
  0 => :FSP_HDR,
  1 => :IBUF_BITMAP,
  2 => :INODE,
  3 => :SYS,
  4 => :INDEX,
  5 => :TRX_SYS,
  6 => :SYS,
  7 => :SYS,
}

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(file, page_size = nil) ⇒ Space

Open a space file, optionally providing the page size to use. Pages that aren’t 16 KiB may not be supported well.



26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/innodb/space.rb', line 26

def initialize(file, page_size=nil)
  @file = File.open(file)
  @size = @file.stat.size

  if page_size
    @page_size = page_size
  else
    @page_size = fsp_flags[:page_size]
  end

  @pages = (@size / @page_size)
  @compressed = fsp_flags[:compressed]
  @record_describer = nil
end

Instance Attribute Details

#page_sizeObject (readonly)

The size (in bytes) of each page in the space.



46
47
48
# File 'lib/innodb/space.rb', line 46

def page_size
  @page_size
end

#pagesObject (readonly)

The number of pages in the space.



52
53
54
# File 'lib/innodb/space.rb', line 52

def pages
  @pages
end

#record_describerObject

An object which can be used to describe records found in pages within this space.



43
44
45
# File 'lib/innodb/space.rb', line 43

def record_describer
  @record_describer
end

#sizeObject (readonly)

The size (in bytes) of the space



49
50
51
# File 'lib/innodb/space.rb', line 49

def size
  @size
end

Instance Method Details

#data_dictionaryObject

Get the Innodb::Page::SysDataDictionaryHeader page for a system space.



209
210
211
# File 'lib/innodb/space.rb', line 209

def data_dictionary
  page(page_sys_data_dictionary) if system_space?
end

#each_indexObject

Iterate through each index by guessing that the root pages will be present starting at page 3, and walking forward until we find a non- root page. This should work fine for IBD files, but not for ibdata files.



229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/innodb/space.rb', line 229

def each_index
  unless block_given?
    return enum_for(:each_index)
  end

  if system_space?
    data_dictionary.each_index do |table_name, index_name, index|
      yield index
    end
  else
    (3...@pages).each do |page_number|
      page = page(page_number)
      if page.type == :INDEX && page.root?
        yield index(page_number)
      else
        break
      end
    end
  end
end

#each_inodeObject

Iterate through Innodb::Inode objects in the space.



267
268
269
270
271
272
273
274
275
276
277
278
279
# File 'lib/innodb/space.rb', line 267

def each_inode
  unless block_given?
    return enum_for(:each_inode)
  end

  each_inode_list.each do |name, list|
    list.each do |page|
      page.each_inode do |inode|
        yield inode
      end
    end
  end
end

#each_inode_listObject

Iterate through Innodb::Inode lists in the space.



256
257
258
259
260
261
262
263
264
# File 'lib/innodb/space.rb', line 256

def each_inode_list
  unless block_given?
    return enum_for(:each_inode_list)
  end

  inode_lists.each do |name|
    yield name, list(name)
  end
end

#each_page(start_page = 0) ⇒ Object

Iterate through all pages in a space, returning the page number and an Innodb::Page object for each one.



283
284
285
286
287
288
289
290
291
292
# File 'lib/innodb/space.rb', line 283

def each_page(start_page=0)
  unless block_given?
    return enum_for(:each_page, start_page)
  end

  (start_page...@pages).each do |page_number|
    current_page = page(page_number)
    yield page_number, current_page if current_page
  end
end

#each_page_status(start_page = 0) ⇒ Object



339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
# File 'lib/innodb/space.rb', line 339

def each_page_status(start_page=0)
  unless block_given?
    return enum_for(:each_page_with_status, start_page)
  end

  each_xdes do |xdes|
    xdes.each_page_status do |page_number, page_status|
      next if page_number < start_page
      next if page_number >= @pages

      if this_page = page(page_number)
        yield page_number, this_page, page_status
      end
    end
  end
end

#each_page_type_region(start_page = 0) {|region| ... } ⇒ Object

Iterate through unique regions in the space by page type. This is useful to achieve an overall view of the space.

Yields:

  • (region)


362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
# File 'lib/innodb/space.rb', line 362

def each_page_type_region(start_page=0)
  unless block_given?
    return enum_for(:each_page_type_region, start_page)
  end

  region = nil
  each_page_status(start_page) do |page_number, page, page_status|
    page_type = type_for_page(page, page_status)
    if region && region[:type] == page_type
      region[:end] = page_number
      region[:count] += 1
    else
      yield region if region
      region = {
        :start => page_number,
        :end   => page_number,
        :type  => page_type,
        :count => 1,
      }
    end
  end
  yield region if region
end

#each_xdesObject

Iterate through all extent descriptors for the space, returning an Innodb::Xdes object for each one.



325
326
327
328
329
330
331
332
333
334
335
336
337
# File 'lib/innodb/space.rb', line 325

def each_xdes
  unless block_given?
    return enum_for(:each_xdes)
  end

  each_xdes_page do |xdes_page|
    xdes_page.each_xdes do |xdes|
      # Only return initialized XDES entries; :state will be nil for extents
      # that have not been allocated yet.
      yield xdes if xdes.xdes[:state]
    end
  end
end

#each_xdes_listObject

Iterate through Innodb::Xdes lists in the space.



300
301
302
303
304
305
306
307
308
# File 'lib/innodb/space.rb', line 300

def each_xdes_list
  unless block_given?
    return enum_for(:each_xdes_list)
  end

  xdes_lists.each do |name|
    yield name, list(name)
  end
end

#each_xdes_pageObject

Iterate through all FSP_HDR/XDES pages, returning an Innodb::Page object for each one.



312
313
314
315
316
317
318
319
320
321
# File 'lib/innodb/space.rb', line 312

def each_xdes_page
  unless block_given?
    return enum_for(:each_xdes_page)
  end

  xdes_page_numbers.each do |page_number|
    current_page = page(page_number)
    yield current_page if current_page
  end
end

#extent_sizeObject

The size (in bytes) of an extent.



91
92
93
# File 'lib/innodb/space.rb', line 91

def extent_size
  1048576
end

#fspObject

Get (and cache) the FSP header from the FSP_HDR page.



176
177
178
# File 'lib/innodb/space.rb', line 176

def fsp
  @fsp ||= page(page_fsp_hdr).fsp_header
end

#fsp_flagsObject

The FSP header flags, decoded. If the page size has not been initialized, reach into the raw bytes of the FSP_HDR page and attempt to decode the flags field that way.



82
83
84
85
86
87
88
# File 'lib/innodb/space.rb', line 82

def fsp_flags
  if @page_size
    return fsp[:flags]
  else
    raw_fsp_header_flags
  end
end

#index(root_page_number, record_describer = nil) ⇒ Object

Get an Innodb::Index object for a specific index by root page number.



221
222
223
# File 'lib/innodb/space.rb', line 221

def index(root_page_number, record_describer=nil)
  Innodb::Index.new(self, root_page_number, record_describer || @record_describer)
end

#inode_listsObject

An array of Innodb::Inode list names.



251
252
253
# File 'lib/innodb/space.rb', line 251

def inode_lists
  [:full_inodes, :free_inodes]
end

#list(name) ⇒ Object

Get an Innodb::List object for a specific list by list name.



214
215
216
217
218
# File 'lib/innodb/space.rb', line 214

def list(name)
  if xdes_lists.include?(name) || inode_lists.include?(name)
    fsp[name]
  end
end

#page(page_number) ⇒ Object

Get an Innodb::Page object for a specific page by page number.



144
145
146
147
148
149
150
151
152
# File 'lib/innodb/space.rb', line 144

def page(page_number)
  this_page = Innodb::Page.parse(self, page_data(page_number))

  if this_page.type == :INDEX
    this_page.record_describer = @record_describer
  end

  this_page
end

#page_data(page_number) ⇒ Object

Get the raw byte buffer for a specific page by page number.



136
137
138
139
140
141
# File 'lib/innodb/space.rb', line 136

def page_data(page_number)
  offset = page_number.to_i * page_size
  return nil unless offset < @size
  return nil unless (offset + page_size) <= @size
  read_at_offset(offset, page_size)
end

#page_fsp_hdrObject

Return the page number for the space’s FSP_HDR page.



171
172
173
# File 'lib/innodb/space.rb', line 171

def page_fsp_hdr
  0
end

#page_sys_data_dictionaryObject

Return the page number for the space’s SYS data dictionary header.



204
205
206
# File 'lib/innodb/space.rb', line 204

def page_sys_data_dictionary
  7
end

#page_trx_sysObject

Return the page number for the space’s TRX_SYS page.



185
186
187
# File 'lib/innodb/space.rb', line 185

def page_trx_sys
  5
end

#pages_per_extentObject

The number of pages per extent.



96
97
98
# File 'lib/innodb/space.rb', line 96

def pages_per_extent
  extent_size / page_size
end

#pages_per_xdes_pageObject

The number of pages per FSP_HDR/XDES page. This is crudely mapped to the page size, and works for pages down to 1KiB.



102
103
104
# File 'lib/innodb/space.rb', line 102

def pages_per_xdes_page
  page_size
end

#raw_fsp_header_flagsObject

Read the FSP header “flags” field by byte offset within the space file. This is useful in order to initialize the page size, as we can’t properly read the FSP_HDR page before we know its size.



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/innodb/space.rb', line 57

def raw_fsp_header_flags
  # A simple sanity check. The FIL header should be initialized in page 0,
  # to offset 0 and page type :FSP_HDR (8).
  page_offset = BinData::Uint32be.read(read_at_offset(4, 4))
  page_type   = BinData::Uint16be.read(read_at_offset(24, 2))
  unless page_offset == 0 && Innodb::Page::PAGE_TYPE_BY_VALUE[page_type] == :FSP_HDR
    raise "Something is very wrong; Page 0 does not seem to be type FSP_HDR"
  end

  # Another sanity check. The Space ID should be the same in both the FIL
  # and FSP headers.
  fil_space = BinData::Uint32be.read(read_at_offset(34, 4))
  fsp_space = BinData::Uint32be.read(read_at_offset(38, 4))
  unless fil_space == fsp_space
    raise "Something is very wrong; FIL and FSP header Space IDs don't match"
  end

  # Well, we're as sure as we can be. Read the flags field and decode it.
  flags_value = BinData::Uint32be.read(read_at_offset(54, 4))
  Innodb::Page::FspHdrXdes.decode_flags(flags_value)
end

#read_at_offset(offset, size) ⇒ Object

Get the raw byte buffer of size bytes at offset in the file.



130
131
132
133
# File 'lib/innodb/space.rb', line 130

def read_at_offset(offset, size)
  @file.seek(offset)
  @file.read(size)
end

#rseg_page?(page_number) ⇒ Boolean

Returns:

  • (Boolean)


194
195
196
197
198
199
200
201
# File 'lib/innodb/space.rb', line 194

def rseg_page?(page_number)
  if trx_sys
    trx_sys.rsegs.include?({
      :space_id => 0,
      :page_number => page_number,
    })
  end
end

#space_idObject



180
181
182
# File 'lib/innodb/space.rb', line 180

def space_id
  fsp[:space_id]
end

#system_space?Boolean

Determine whether this space looks like a system space. If the initial pages in the space match the SYSTEM_SPACE_PAGE_MAP, it is likely to be a system space.

Returns:

  • (Boolean)


157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/innodb/space.rb', line 157

def system_space?
  SYSTEM_SPACE_PAGE_MAP.each do |page_number, type|
    # We can't use page() here, because system_space? need to be used
    # in the Innodb::Page::Sys.parse to determine what type of page
    # is being looked at. Using page() would cause us to keep recurse
    # infinitely. Use Innodb::Page.new instead to load the page as
    # simply as possible.
    test_page = Innodb::Page.new(self, page_data(page_number))
    return false unless test_page.type == type
  end
  true
end

#trx_sysObject

Get the Innodb::Page::TrxSys page for a system space.



190
191
192
# File 'lib/innodb/space.rb', line 190

def trx_sys
  page(page_trx_sys) if system_space?
end

#type_for_page(page, page_status) ⇒ Object



356
357
358
# File 'lib/innodb/space.rb', line 356

def type_for_page(page, page_status)
  page_status[:free] ? "FREE (#{page.type})" : page.type
end

#xdes_entry_for_page(page_number) ⇒ Object

The XDES entry offset for a given page within its FSP_HDR/XDES page’s XDES array.



118
119
120
121
# File 'lib/innodb/space.rb', line 118

def xdes_entry_for_page(page_number)
  relative_page_number = page_number - xdes_page_for_page(page_number)
  relative_page_number / pages_per_extent
end

#xdes_for_page(page_number) ⇒ Object

Return the Innodb::Xdes entry which represents a given page.



124
125
126
127
# File 'lib/innodb/space.rb', line 124

def xdes_for_page(page_number)
  xdes_array = page(xdes_page_for_page(page_number)).each_xdes.to_a
  xdes_array[xdes_entry_for_page(page_number)]
end

#xdes_listsObject

An array of Innodb::Xdes list names.



295
296
297
# File 'lib/innodb/space.rb', line 295

def xdes_lists
  [:free, :free_frag, :full_frag]
end

#xdes_page_for_page(page_number) ⇒ Object

The FSP_HDR/XDES page which will contain the XDES entry for a given page.



112
113
114
# File 'lib/innodb/space.rb', line 112

def xdes_page_for_page(page_number)
  page_number - (page_number % pages_per_xdes_page)
end

#xdes_page_numbersObject

An array of all FSP/XDES page numbers for the space.



107
108
109
# File 'lib/innodb/space.rb', line 107

def xdes_page_numbers
  (0..(@pages / pages_per_xdes_page)).map { |n| n * pages_per_xdes_page }
end