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.



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

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]
  @innodb_system = nil
  @record_describer = nil
end

Instance Attribute Details

#innodb_system ⇒ Object

The Innodb::System to which this space belongs, if any.



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

def innodb_system
  @innodb_system
end

#page_size ⇒ Object (readonly)

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



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

def page_size
  @page_size
end

#pages ⇒ Object (readonly)

The number of pages in the space.



55
56
57
# File 'lib/innodb/space.rb', line 55

def pages
  @pages
end

#record_describer ⇒ Object

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



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

def record_describer
  @record_describer
end

#size ⇒ Object (readonly)

The size (in bytes) of the space



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

def size
  @size
end

Instance Method Details

#data_dictionary_page ⇒ Object

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



206
207
208
# File 'lib/innodb/space.rb', line 206

def data_dictionary_page
  page(page_sys_data_dictionary) if system_space?
end

#each_index ⇒ Object

Iterate through all indexes in the space.



250
251
252
253
254
255
256
257
258
259
260
# File 'lib/innodb/space.rb', line 250

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

  each_index_root_page_number do |page_number|
    yield index(page_number)
  end

  nil
end

#each_index_root_page_number ⇒ Object

Iterate through all root page numbers for indexes in the space.



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

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

  if innodb_system
    # Retrieve the index root page numbers from the data dictionary.
    innodb_system.data_dictionary.each_index_by_space_id(space_id) do |record|
      yield record["PAGE_NO"]
    end
  else
    # Guess that the index root pages will be present starting at page 3,
    # and walk forward until we find a non-root page. This should work fine
    # for IBD files, if they haven't added indexes online.
    (3...@pages).each do |page_number|
      page = page(page_number)
      if page.type == :INDEX && page.root?
        yield page_number
      end
    end
  end

  nil
end

#each_inode ⇒ Object

Iterate through Innodb::Inode objects in the space.



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

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_list ⇒ Object

Iterate through Innodb::Inode lists in the space.



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

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.



295
296
297
298
299
300
301
302
303
304
# File 'lib/innodb/space.rb', line 295

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

Iterate through all pages, yielding the page number, page object, and page status.



353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
# File 'lib/innodb/space.rb', line 353

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)


377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
# File 'lib/innodb/space.rb', line 377

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_xdes ⇒ Object

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



337
338
339
340
341
342
343
344
345
346
347
348
349
# File 'lib/innodb/space.rb', line 337

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_list ⇒ Object

Iterate through Innodb::Xdes lists in the space.



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

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_page ⇒ Object

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



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

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_size ⇒ Object

The size (in bytes) of an extent.



94
95
96
# File 'lib/innodb/space.rb', line 94

def extent_size
  1048576
end

#fsp ⇒ Object

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



173
174
175
# File 'lib/innodb/space.rb', line 173

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

#fsp_flags ⇒ Object

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.



85
86
87
88
89
90
91
# File 'lib/innodb/space.rb', line 85

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.



218
219
220
221
# File 'lib/innodb/space.rb', line 218

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

#inode_lists ⇒ Object

An array of Innodb::Inode list names.



263
264
265
# File 'lib/innodb/space.rb', line 263

def inode_lists
  [:full_inodes, :free_inodes]
end

#list(name) ⇒ Object

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



211
212
213
214
215
# File 'lib/innodb/space.rb', line 211

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.



147
148
149
# File 'lib/innodb/space.rb', line 147

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

#page_data(page_number) ⇒ Object

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



139
140
141
142
143
144
# File 'lib/innodb/space.rb', line 139

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_hdr ⇒ Object

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



168
169
170
# File 'lib/innodb/space.rb', line 168

def page_fsp_hdr
  0
end

#page_sys_data_dictionary ⇒ Object

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



201
202
203
# File 'lib/innodb/space.rb', line 201

def page_sys_data_dictionary
  7
end

#page_trx_sys ⇒ Object

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



182
183
184
# File 'lib/innodb/space.rb', line 182

def page_trx_sys
  5
end

#pages_per_extent ⇒ Object

The number of pages per extent.



99
100
101
# File 'lib/innodb/space.rb', line 99

def pages_per_extent
  extent_size / page_size
end

#pages_per_xdes_page ⇒ Object

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



105
106
107
# File 'lib/innodb/space.rb', line 105

def pages_per_xdes_page
  page_size
end

#raw_fsp_header_flags ⇒ Object

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.



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

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.



133
134
135
136
# File 'lib/innodb/space.rb', line 133

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

#rseg_page?(page_number) ⇒ Boolean

Returns:

  • (Boolean)


191
192
193
194
195
196
197
198
# File 'lib/innodb/space.rb', line 191

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

#space_id ⇒ Object



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

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)


154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/innodb/space.rb', line 154

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_sys ⇒ Object

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



187
188
189
# File 'lib/innodb/space.rb', line 187

def trx_sys
  page(page_trx_sys) if system_space?
end

#type_for_page(page, page_status) ⇒ Object

A helper to produce a printable page type.



371
372
373
# File 'lib/innodb/space.rb', line 371

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.



121
122
123
124
# File 'lib/innodb/space.rb', line 121

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.



127
128
129
130
# File 'lib/innodb/space.rb', line 127

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_lists ⇒ Object

An array of Innodb::Xdes list names.



307
308
309
# File 'lib/innodb/space.rb', line 307

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.



115
116
117
# File 'lib/innodb/space.rb', line 115

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

#xdes_page_numbers ⇒ Object

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



110
111
112
# File 'lib/innodb/space.rb', line 110

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