Class: Innodb::Page

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Defined in:
lib/innodb/page.rb,
lib/innodb/page/sys.rb,
lib/innodb/page/blob.rb,
lib/innodb/page/index.rb,
lib/innodb/page/inode.rb,
lib/innodb/page/trx_sys.rb,
lib/innodb/page/undo_log.rb,
lib/innodb/page/ibuf_bitmap.rb,
lib/innodb/page/fsp_hdr_xdes.rb,
lib/innodb/page/sys_ibuf_header.rb,
lib/innodb/page/sys_rseg_header.rb,
lib/innodb/page/index_compressed.rb,
lib/innodb/page/sys_data_dictionary_header.rb

Defined Under Namespace

Classes: Address, Blob, FilHeader, FilTrailer, FspHdrXdes, IbufBitmap, Index, IndexCompressed, Inode, Region, Sys, SysDataDictionaryHeader, SysIbufHeader, SysRsegHeader, TrxSys, UndoLog

Constant Summary collapse

PAGE_TYPE =

InnoDB Page Type constants from include/fil0fil.h.

{
  ALLOCATED: {
    value: 0,
    description: "Freshly allocated",
    usage: "page type field has not been initialized",
  },
  UNDO_LOG: {
    value: 2,
    description: "Undo log",
    usage: "stores previous values of modified records",
  },
  INODE: {
    value: 3,
    description: "File segment inode",
    usage: "bookkeeping for file segments",
  },
  IBUF_FREE_LIST: {
    value: 4,
    description: "Insert buffer free list",
    usage: "bookkeeping for insert buffer free space management",
  },
  IBUF_BITMAP: {
    value: 5,
    description: "Insert buffer bitmap",
    usage: "bookkeeping for insert buffer writes to be merged",
  },
  SYS: {
    value: 6,
    description: "System internal",
    usage: "used for various purposes in the system tablespace",
  },
  TRX_SYS: {
    value: 7,
    description: "Transaction system header",
    usage: "bookkeeping for the transaction system in system tablespace",
  },
  FSP_HDR: {
    value: 8,
    description: "File space header",
    usage: "header page (page 0) for each tablespace file",
  },
  XDES: {
    value: 9,
    description: "Extent descriptor",
    usage: "header page for subsequent blocks of 16,384 pages",
  },
  BLOB: {
    value: 10,
    description: "Uncompressed BLOB",
    usage: "externally-stored uncompressed BLOB column data",
  },
  ZBLOB: {
    value: 11,
    description: "First compressed BLOB",
    usage: "externally-stored compressed BLOB column data, first page",
  },
  ZBLOB2: {
    value: 12,
    description: "Subsequent compressed BLOB",
    usage: "externally-stored compressed BLOB column data, subsequent page",
  },
  INDEX: {
    value: 17_855,
    description: "B+Tree index",
    usage: "table and index data stored in B+Tree structure",
  },
}.freeze
PAGE_TYPE_BY_VALUE =
PAGE_TYPE.each_with_object({}) { |(k, v), h| h[v[:value]] = k }
UNDEFINED_PAGE_NUMBER =

A page number representing “undefined” values, (4294967295).

2**32 - 1

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(space, buffer, page_number = nil) ⇒ Page

Initialize a page by passing in a buffer containing the raw page contents. The buffer size should match the space’s page size.



103
104
105
106
107
108
109
110
111
112
113
# File 'lib/innodb/page.rb', line 103

def initialize(space, buffer, page_number = nil)
  unless space && buffer
    raise "Page can't be initialized from nil space or buffer (space: #{space}, buffer: #{buffer})"
  end

  raise "Buffer size #{buffer.size} is different than space page size" unless space.page_size == buffer.size

  @space  = space
  @buffer = buffer
  @page_number = page_number
end

Class Attribute Details

.specialized_classesObject (readonly)

Returns the value of attribute specialized_classes.



57
58
59
# File 'lib/innodb/page.rb', line 57

def specialized_classes
  @specialized_classes
end

Instance Attribute Details

#spaceObject (readonly)

Returns the value of attribute space.



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

def space
  @space
end

Class Method Details

.handle(_page, space, buffer, page_number = nil) ⇒ Object

Allow the specialized class to do something that isn’t ‘new’ with this page.



97
98
99
# File 'lib/innodb/page.rb', line 97

def self.handle(_page, space, buffer, page_number = nil)
  new(space, buffer, page_number)
end

.maybe_undefined(page_number) ⇒ Object

A helper to convert “undefined” values stored in previous and next pointers in the page header to nil.



284
285
286
# File 'lib/innodb/page.rb', line 284

def self.maybe_undefined(page_number)
  page_number unless undefined?(page_number)
end

.parse(space, buffer, page_number = nil) ⇒ Object

Load a page as a generic page in order to make the “fil” header accessible, and then attempt to hand off the page to a specialized class to be re-parsed if possible. If there is no specialized class for this type of page, return the generic object.

This could be optimized to reach into the page buffer and efficiently extract the page type in order to avoid throwing away a generic Innodb::Page object when parsing every specialized page, but this is a bit cleaner, and we’re not particularly performance sensitive.



83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/innodb/page.rb', line 83

def self.parse(space, buffer, page_number = nil)
  # Create a page object as a generic page.
  page = Innodb::Page.new(space, buffer, page_number)

  # If there is a specialized class available for this page type, re-create
  # the page object using that specialized class.
  if (specialized_class = specialized_classes[page.type])
    page = specialized_class.handle(page, space, buffer, page_number)
  end

  page
end

.register_specialization(page_type, specialized_class) ⇒ Object



60
61
62
# File 'lib/innodb/page.rb', line 60

def self.register_specialization(page_type, specialized_class)
  @specialized_classes[page_type] = specialized_class
end

.specialization_for(page_type) ⇒ Object



64
65
66
67
68
# File 'lib/innodb/page.rb', line 64

def self.specialization_for(page_type)
  # This needs to intentionally use Innodb::Page because we need to register
  # in the class instance variable in *that* class.
  Innodb::Page.register_specialization(page_type, self)
end

.specialization_for?(page_type) ⇒ Boolean

Returns:

  • (Boolean)


70
71
72
# File 'lib/innodb/page.rb', line 70

def self.specialization_for?(page_type)
  Innodb::Page.specialized_classes.include?(page_type)
end

.undefined?(page_number) ⇒ Boolean

A helper to check if a page number is the undefined page number.

Returns:

  • (Boolean)


278
279
280
# File 'lib/innodb/page.rb', line 278

def self.undefined?(page_number)
  page_number == UNDEFINED_PAGE_NUMBER
end

Instance Method Details

#checksum_crc32Object

Calculate the checksum of the page using the CRC32c algorithm.



358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
# File 'lib/innodb/page.rb', line 358

def checksum_crc32
  raise "Checksum calculation is only supported for 16 KiB pages" unless default_page_size?

  @checksum_crc32 ||= begin
    # Calculate the CRC32c of the page header.
    crc_partial_header = Digest::CRC32c.new
    each_page_header_byte_as_uint8 do |byte|
      crc_partial_header << byte.chr
    end

    # Calculate the CRC32c of the page body.
    crc_page_body = Digest::CRC32c.new
    each_page_body_byte_as_uint8 do |byte|
      crc_page_body << byte.chr
    end

    # Bitwise XOR the two checksums together.
    crc_partial_header.checksum ^ crc_page_body.checksum
  end
end

#checksum_crc32?Boolean

Returns:

  • (Boolean)


379
380
381
# File 'lib/innodb/page.rb', line 379

def checksum_crc32?
  checksum == checksum_crc32
end

#checksum_innodbObject

Calculate the checksum of the page using InnoDB’s algorithm.



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

def checksum_innodb
  raise "Checksum calculation is only supported for 16 KiB pages" unless default_page_size?

  @checksum_innodb ||= begin
    # Calculate the InnoDB checksum of the page header.
    c_partial_header = Innodb::Checksum.fold_enumerator(each_page_header_byte_as_uint8)

    # Calculate the InnoDB checksum of the page body.
    c_page_body = Innodb::Checksum.fold_enumerator(each_page_body_byte_as_uint8)

    # Add the two checksums together, and mask the result back to 32 bits.
    (c_partial_header + c_page_body) & Innodb::Checksum::MAX
  end
end

#checksum_innodb?Boolean

Returns:

  • (Boolean)


353
354
355
# File 'lib/innodb/page.rb', line 353

def checksum_innodb?
  checksum == checksum_innodb
end

#checksum_invalid?Boolean

Is the page checksum incorrect?

Returns:

  • (Boolean)


389
390
391
# File 'lib/innodb/page.rb', line 389

def checksum_invalid?
  !checksum_valid?
end

#checksum_typeObject



393
394
395
396
397
398
# File 'lib/innodb/page.rb', line 393

def checksum_type
  return :crc32 if checksum_crc32?
  return :innodb if checksum_innodb?

  nil
end

#checksum_valid?Boolean

Is the page checksum correct?

Returns:

  • (Boolean)


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

def checksum_valid?
  checksum_crc32? || checksum_innodb?
end

#corrupt?Boolean

Is the page corrupt, either due to data corruption, tearing, or in the wrong place?

Returns:

  • (Boolean)


430
431
432
# File 'lib/innodb/page.rb', line 430

def corrupt?
  checksum_invalid? || torn? || misplaced?
end

#cursor(buffer_offset) ⇒ Object

If no block is passed, return an BufferCursor object positioned at a specific offset. If a block is passed, create a cursor at the provided offset and yield it to the provided block one time, and then return the return value of the block.



142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/innodb/page.rb', line 142

def cursor(buffer_offset)
  new_cursor = BufferCursor.new(@buffer, buffer_offset)
  new_cursor.push_name("space[#{space.name}]")
  new_cursor.push_name("page[#{name}]")

  if block_given?
    # Call the block once and return its return value.
    yield new_cursor
  else
    # Return the cursor itself.
    new_cursor
  end
end

#default_page_size?Boolean

Returns:

  • (Boolean)


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

def default_page_size?
  size == Innodb::Space::DEFAULT_PAGE_SIZE
end

#dumpObject

Dump the contents of a page for debugging purposes.



483
484
485
486
487
488
489
490
491
492
493
494
# File 'lib/innodb/page.rb', line 483

def dump
  puts "#{self}:"
  puts

  puts "fil header:"
  pp fil_header
  puts

  puts "fil trailer:"
  pp fil_trailer
  puts
end

#each_page_body_byte_as_uint8(&block) ⇒ Object

Iterate each byte of the page body, except for the FIL header and the FIL trailer.



331
332
333
334
335
# File 'lib/innodb/page.rb', line 331

def each_page_body_byte_as_uint8(&block)
  return enum_for(:each_page_body_byte_as_uint8) unless block_given?

  cursor(pos_page_body).each_byte_as_uint8(size_page_body, &block)
end

#each_page_header_byte_as_uint8(&block) ⇒ Object

Iterate each byte of the FIL header.



323
324
325
326
327
# File 'lib/innodb/page.rb', line 323

def each_page_header_byte_as_uint8(&block)
  return enum_for(:each_page_header_byte_as_uint8) unless block_given?

  cursor(pos_partial_page_header).each_byte_as_uint8(size_partial_page_header, &block)
end

#each_region {|Region.new( offset: pos_fil_header, length: size_fil_header, name: :fil_header, info: "FIL Header" )| ... } ⇒ Object

Yields:



439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
# File 'lib/innodb/page.rb', line 439

def each_region
  return enum_for(:each_region) unless block_given?

  yield Region.new(
    offset: pos_fil_header,
    length: size_fil_header,
    name: :fil_header,
    info: "FIL Header"
  )

  yield Region.new(
    offset: pos_fil_trailer,
    length: size_fil_trailer,
    name: :fil_trailer,
    info: "FIL Trailer"
  )

  nil
end

#extent_descriptor?Boolean

Is this an extent descriptor page (either FSP_HDR or XDES)?

Returns:

  • (Boolean)


435
436
437
# File 'lib/innodb/page.rb', line 435

def extent_descriptor?
  type == :FSP_HDR || type == :XDES
end

#fil_headerObject

Return the “fil” header from the page, which is common for all page types.



289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/innodb/page.rb', line 289

def fil_header
  @fil_header ||= cursor(pos_fil_header).name("fil_header") do |c|
    FilHeader.new(
      checksum: c.name("checksum") { c.read_uint32 },
      offset: c.name("offset") { c.read_uint32 },
      prev: c.name("prev") { Innodb::Page.maybe_undefined(c.read_uint32) },
      next: c.name("next") { Innodb::Page.maybe_undefined(c.read_uint32) },
      lsn: c.name("lsn") { c.read_uint64 },
      type: c.name("type") { PAGE_TYPE_BY_VALUE[c.read_uint16] },
      flush_lsn: c.name("flush_lsn") { c.read_uint64 },
      space_id: c.name("space_id") { c.read_uint32 }
    )
  end
end

#fil_trailerObject

Return the “fil” trailer from the page, which is common for all page types.



305
306
307
308
309
310
311
312
# File 'lib/innodb/page.rb', line 305

def fil_trailer
  @fil_trailer ||= cursor(pos_fil_trailer).name("fil_trailer") do |c|
    FilTrailer.new(
      checksum: c.name("checksum") { c.read_uint32 },
      lsn_low32: c.name("lsn_low32") { c.read_uint32 }
    )
  end
end

#in_doublewrite_buffer?Boolean

Is the page in the doublewrite buffer?

Returns:

  • (Boolean)


407
408
409
# File 'lib/innodb/page.rb', line 407

def in_doublewrite_buffer?
  space&.system_space? && space&.doublewrite_page?(offset)
end

#inspectObject

Implement a custom inspect method to avoid irb printing the contents of the page buffer, since it’s very large and mostly not interesting.



478
479
480
# File 'lib/innodb/page.rb', line 478

def inspect
  "#<#{self.class} #{inspect_header_fields || '(page header unavailable)'}>"
end

#inspect_header_fieldsObject



459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
# File 'lib/innodb/page.rb', line 459

def inspect_header_fields
  return nil unless fil_header

  %i[
    size
    space_id
    offset
    type
    prev
    next
    checksum_valid?
    checksum_type
    torn?
    misplaced?
  ].map { |m| "#{m}=#{send(m).inspect}" }.join(", ")
end

#misplaced?Boolean

Is the page misplaced in the wrong file or by offset in the file?

Returns:

  • (Boolean)


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

def misplaced?
  !in_doublewrite_buffer? && (misplaced_space? || misplaced_offset?)
end

#misplaced_offset?Boolean

Is the page number stored in the header different from the page number which was supposed to be read?

Returns:

  • (Boolean)


419
420
421
# File 'lib/innodb/page.rb', line 419

def misplaced_offset?
  offset != @page_number
end

#misplaced_space?Boolean

Is the space ID stored in the header different from that of the space provided when initializing this page?

Returns:

  • (Boolean)


413
414
415
# File 'lib/innodb/page.rb', line 413

def misplaced_space?
  space && (space_id != space.space_id)
end

#nameObject

Return a simple string to uniquely identify this page within the space. Be careful not to call anything which would instantiate a BufferCursor so that we can use this method in cursor initialization.



129
130
131
132
133
134
135
136
# File 'lib/innodb/page.rb', line 129

def name
  page_offset = BinData::Uint32be.read(@buffer.slice(4, 4))
  page_type = BinData::Uint16be.read(@buffer.slice(24, 2))
  "%i,%s" % [
    page_offset,
    PAGE_TYPE_BY_VALUE[page_type],
  ]
end

#pos_fil_headerObject

Return the byte offset of the start of the “fil” header, which is at the beginning of the page. Included here primarily for completeness.



158
159
160
# File 'lib/innodb/page.rb', line 158

def pos_fil_header
  0
end

#pos_fil_trailerObject

Return the byte offset of the start of the “fil” trailer, which is at the end of the page.



183
184
185
# File 'lib/innodb/page.rb', line 183

def pos_fil_trailer
  size - size_fil_trailer
end

#pos_page_bodyObject

Return the position of the “body” of the page, which starts after the FIL header.



194
195
196
# File 'lib/innodb/page.rb', line 194

def pos_page_body
  pos_fil_header + size_fil_header
end

#pos_partial_page_headerObject

The start of the checksummed portion of the file header.



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

def pos_partial_page_header
  pos_fil_header + 4
end

#sizeObject

Return the page size, to eventually be able to deal with non-16kB pages.



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

def size
  @size ||= @buffer.size
end

#size_fil_headerObject

Return the size of the “fil” header, in bytes.



163
164
165
# File 'lib/innodb/page.rb', line 163

def size_fil_header
  4 + 4 + 4 + 4 + 8 + 2 + 8 + 4
end

#size_fil_trailerObject

Return the size of the “fil” trailer, in bytes.



188
189
190
# File 'lib/innodb/page.rb', line 188

def size_fil_trailer
  4 + 4
end

#size_page_bodyObject

Return the size of the page body, excluding the header and trailer.



199
200
201
# File 'lib/innodb/page.rb', line 199

def size_page_body
  size - size_fil_trailer - size_fil_header
end

#size_partial_page_headerObject

The size of the portion of the fil header that is included in the checksum. Exclude the following:

:checksum   (offset 4, size 4)
:flush_lsn  (offset 26, size 8)
:space_id   (offset 34, size 4)


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

def size_partial_page_header
  size_fil_header - 4 - 8 - 4
end

#torn?Boolean

Is the LSN stored in the header different from the one stored in the trailer?

Returns:

  • (Boolean)


402
403
404
# File 'lib/innodb/page.rb', line 402

def torn?
  fil_header.lsn_low32 != fil_trailer.lsn_low32
end