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.

Defined Under Namespace

Classes: DataFile

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(filenames) ⇒ Space

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



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/innodb/space.rb', line 46

def initialize(filenames)
  filenames = [filenames] unless filenames.is_a?(Array)

  @data_files = []
  @size = 0
  filenames.each do |filename|
    file = DataFile.new(filename, @size)
    @size += file.size
    @data_files << file
  end

  @system_page_size = fsp_flags[:system_page_size]
  @page_size        = fsp_flags[:page_size]
  @compressed       = fsp_flags[:compressed]

  @pages = (@size / @page_size)
  @innodb_system = nil
  @record_describer = nil
end

Instance Attribute Details

#innodb_systemObject

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



67
68
69
# File 'lib/innodb/space.rb', line 67

def innodb_system
  @innodb_system
end

#page_sizeObject (readonly)

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



77
78
79
# File 'lib/innodb/space.rb', line 77

def page_size
  @page_size
end

#pagesObject (readonly)

The number of pages in the space.



83
84
85
# File 'lib/innodb/space.rb', line 83

def pages
  @pages
end

#record_describerObject

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



71
72
73
# File 'lib/innodb/space.rb', line 71

def record_describer
  @record_describer
end

#sizeObject (readonly)

The size (in bytes) of the space



80
81
82
# File 'lib/innodb/space.rb', line 80

def size
  @size
end

#system_page_sizeObject (readonly)

The system default page size (in bytes), equivalent to UNIV_PAGE_SIZE.



74
75
76
# File 'lib/innodb/space.rb', line 74

def system_page_size
  @system_page_size
end

Instance Method Details

#data_dictionary_pageObject

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



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

def data_dictionary_page
  page(page_sys_data_dictionary) if system_space?
end

#data_file_for_offset(offset) ⇒ Object



208
209
210
211
212
213
214
# File 'lib/innodb/space.rb', line 208

def data_file_for_offset(offset)
  @data_files.each do |file|
    return file if offset < file.size
    offset -= file.size
  end
  nil
end

#doublewrite_page?(page_number) ⇒ Boolean

Return true if a page is in the doublewrite buffer.

Returns:

  • (Boolean)


404
405
406
407
408
# File 'lib/innodb/space.rb', line 404

def doublewrite_page?(page_number)
  return false unless system_space?
  @doublewrite_pages ||= each_doublewrite_page_number.to_a
  @doublewrite_pages.include?(page_number)
end

#each_doublewrite_page_numberObject

Iterate through the page numbers in the doublewrite buffer.



389
390
391
392
393
394
395
396
397
398
399
400
401
# File 'lib/innodb/space.rb', line 389

def each_doublewrite_page_number
  return nil unless system_space?

  unless block_given?
    return enum_for(:each_doublewrite_page_number)
  end

  trx_sys.doublewrite[:page_info][0][:page_number].each do |start_page|
    (start_page...(start_page+pages_per_extent)).each do |page_number|
      yield page_number
    end
  end
end

#each_indexObject

Iterate through all indexes in the space.



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

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_numberObject

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



313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
# File 'lib/innodb/space.rb', line 313

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_inodeObject

Iterate through Innodb::Inode objects in the space.



368
369
370
371
372
373
374
375
376
377
378
379
380
# File 'lib/innodb/space.rb', line 368

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_allocated_inode do |inode|
        yield inode
      end
    end
  end
end

#each_inode_listObject

Iterate through Innodb::Inode lists in the space.



357
358
359
360
361
362
363
364
365
# File 'lib/innodb/space.rb', line 357

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.



412
413
414
415
416
417
418
419
420
421
# File 'lib/innodb/space.rb', line 412

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.



481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
# File 'lib/innodb/space.rb', line 481

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)


505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
# File 'lib/innodb/space.rb', line 505

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.



465
466
467
468
469
470
471
472
473
474
475
476
477
# File 'lib/innodb/space.rb', line 465

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.



429
430
431
432
433
434
435
436
437
# File 'lib/innodb/space.rb', line 429

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.



452
453
454
455
456
457
458
459
460
461
# File 'lib/innodb/space.rb', line 452

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

  each_xdes_page_number do |page_number|
    current_page = page(page_number)
    yield current_page if current_page and [:FSP_HDR, :XDES].include?(current_page.type)
  end
end

#each_xdes_page_numberObject

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



440
441
442
443
444
445
446
447
448
# File 'lib/innodb/space.rb', line 440

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

  0.step(pages - 1, pages_per_bookkeeping_page).each do |n|
    yield n
  end
end

#extent_sizeObject

The size (in bytes) of an extent.



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

def extent_size
  pages_per_extent * page_size
end

#fspObject

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



261
262
263
# File 'lib/innodb/space.rb', line 261

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.



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

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

#ibuf_bitmap_page_for_page(page_number) ⇒ Object

The IBUF_BITMAP page which will contain the bitmap entry for a given page.



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

def ibuf_bitmap_page_for_page(page_number)
  page_number - (page_number % pages_per_bookkeeping_page) + 1
end

#index(root_page_number, record_describer = nil) ⇒ Object

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



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

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

#inode(fseg_id) ⇒ Object

Return an Inode by fseg_id. Iterates through the inode list, but it normally is fairly small, so should be relatively efficient.



384
385
386
# File 'lib/innodb/space.rb', line 384

def inode(fseg_id)
  each_inode.select { |inode| inode.fseg_id == fseg_id }.first
end

#inode_listsObject

An array of Innodb::Inode list names.



352
353
354
# File 'lib/innodb/space.rb', line 352

def inode_lists
  [:full_inodes, :free_inodes]
end

#inspectObject



92
93
94
95
96
97
98
99
# File 'lib/innodb/space.rb', line 92

def inspect
  "<%s file=%s, page_size=%i, pages=%i>" % [
    self.class.name,
    name.inspect,
    page_size,
    pages,
  ]
end

#list(name) ⇒ Object

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



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

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

#nameObject

Return a string which can uniquely identify this space. Be careful not to do anything which could instantiate a BufferCursor so that we can use this method in cursor initialization.



88
89
90
# File 'lib/innodb/space.rb', line 88

def name
  @name ||= @data_files.map { |f| f.name }.join(",")
end

#page(page_number) ⇒ Object

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



230
231
232
233
234
235
236
237
# File 'lib/innodb/space.rb', line 230

def page(page_number)
  data = page_data(page_number)
  if data
    Innodb::Page.parse(self, data, page_number)
  else
    nil
  end
end

#page_data(page_number) ⇒ Object

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



225
226
227
# File 'lib/innodb/space.rb', line 225

def page_data(page_number)
  read_at_offset(page_number * page_size, page_size)
end

#page_fsp_hdrObject

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



256
257
258
# File 'lib/innodb/space.rb', line 256

def page_fsp_hdr
  0
end

#page_sys_data_dictionaryObject

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



290
291
292
# File 'lib/innodb/space.rb', line 290

def page_sys_data_dictionary
  7
end

#page_trx_sysObject

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



270
271
272
# File 'lib/innodb/space.rb', line 270

def page_trx_sys
  5
end

#pages_per_bookkeeping_pageObject

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



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

def pages_per_bookkeeping_page
  page_size
end

#pages_per_extentObject

The number of pages per extent.



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/innodb/space.rb', line 144

def pages_per_extent
  # Note that uncompressed tables and compressed tables using the same page
  # size will have a different number of pages per "extent" because InnoDB
  # compression uses the FSP_EXTENT_SIZE define (which is then based on the
  # UNIV_PAGE_SIZE define, which may be based on the innodb_page_size system
  # variable) for compressed tables rather than something based on the actual
  # compressed page size.
  #
  # For this reason, an "extent" differs in size as follows (the maximum page
  # size supported for compressed tables is the innodb_page_size):
  #
  #   innodb_page_size                | innodb compression              |
  #   page size | extent size | pages | page size | extent size | pages |
  #   16384     | 1 MiB       | 64    | 16384     | 1 MiB       | 64    |
  #                                   | 8192      | 512 KiB     | 64    |
  #                                   | 4096      | 256 KiB     | 64    |
  #                                   | 2048      | 128 KiB     | 64    |
  #                                   | 1024      | 64 KiB      | 64    |
  #   8192      | 1 MiB       | 128   | 8192      | 1 MiB       | 128   |
  #                                   | 4096      | 512 KiB     | 128   |
  #                                   | 2048      | 256 KiB     | 128   |
  #                                   | 1024      | 128 KiB     | 128   |
  #   4096      | 1 MiB       | 256   | 4096      | 1 MiB       | 256   |
  #                                   | 2048      | 512 KiB     | 256   |
  #                                   | 1024      | 256 KiB     | 256   |
  #

  1048576 / system_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.



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/innodb/space.rb', line 104

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)).to_i
  page_type   = BinData::Uint16be.read(read_at_offset(24, 2)).to_i
  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; got page type %i but expected %i" % [
      page_type,
      Innodb::Page::PAGE_TYPE[:FSP_HDR][:value],
    ]
  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)).to_i
  fsp_space = BinData::Uint32be.read(read_at_offset(38, 4)).to_i
  unless fil_space == fsp_space
    raise "Something is very wrong; FIL and FSP header Space IDs don't match: FIL is %i but FSP is %i" % [
      fil_space,
      fsp_space,
    ]
  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.



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

def read_at_offset(offset, size)
  return nil unless offset < @size && (offset + size) <= @size
  data_file = data_file_for_offset(offset)
  data_file.file.seek(offset - data_file.offset)
  data_file.file.read(size)
end

#rseg_page?(page_number) ⇒ Boolean

Returns:

  • (Boolean)


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

def rseg_page?(page_number)
  if trx_sys
    rseg_match = trx_sys.rsegs.select { |rseg|
      rseg[:space_id] == 0 && rseg[:page_number] == page_number
    }

    ! rseg_match.empty?
  end
end

#space_idObject



265
266
267
# File 'lib/innodb/space.rb', line 265

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)


242
243
244
245
246
247
248
249
250
251
252
253
# File 'lib/innodb/space.rb', line 242

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.



275
276
277
# File 'lib/innodb/space.rb', line 275

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.



499
500
501
# File 'lib/innodb/space.rb', line 499

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.



197
198
199
200
# File 'lib/innodb/space.rb', line 197

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.



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

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.



424
425
426
# File 'lib/innodb/space.rb', line 424

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.



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

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