Является ли COMB GUID хорошей идеей для Rails 3.1, если я использую GUID для первичных ключей? - PullRequest
3 голосов
/ 13 октября 2011

Я использую Rails 3.1 с PostgreSQL 8.4.Давайте предположим, что я хочу / должен использовать первичные ключи GUID.Одним из потенциальных недостатков является фрагментация индекса.В MS SQL для этого рекомендуется использовать специальные последовательные идентификаторы GUID.Один подход к последовательным GUID - это GUID COMBination, который заменяет 6-байтовую временную метку для части MAC-адреса в конце GUID.Это имеет широкое распространение: COMB доступны в NHibernate ( NHibernate / Id / GuidCombGenerator.cs ).

Я думаю, что я понял, как создавать GUID COMB в Rails (с помощьюсправка из UUIDTools 2.1.2), но он оставляет некоторые вопросы без ответа:

  • Страдает ли PostgreSQL от фрагментации индекса, когда PRIMARY KEY имеет тип UUID?
  • Избегается ли фрагментацияесли младшие 6 байтов GUID являются последовательными?
  • Является ли COMB GUID, реализованный ниже, приемлемым и надежным способом создания последовательных GUID в Rails?

Спасибо за ваши мысли.


create_contacts.rb миграция

class CreateContacts < ActiveRecord::Migration
  def up
    create_table :contacts, :id => false do |t|
      t.column :id, :uuid, :null => false # manually create :id with underlying DB type UUID
      t.string :first_name
      t.string :last_name
      t.string :email

      t.timestamps
    end
    execute "ALTER TABLE contacts ADD PRIMARY KEY (id);"
  end

    # Can't use reversible migration because it will try to run 'execute' again
  def down
    drop_table :contacts # also drops primary key
  end
end

/app/models/contact.rb

class Contact < ActiveRecord::Base
  require 'uuid_helper' #rails 3 does not autoload from lib/*
  include UUIDHelper

  set_primary_key :id
end

/lib/uuid_tools.rb

require 'uuidtools'

module UUIDHelper
  def self.included(base)
    base.class_eval do
      include InstanceMethods
      attr_readonly :id       # writable only on a new record
      before_create :set_uuid
    end
  end

  module InstanceMethods
  private
    def set_uuid
      # MS SQL syntax:  CAST(CAST(NEWID() AS BINARY(10)) + CAST(GETDATE() AS BINARY(6)) AS UNIQUEIDENTIFIER)

      # Get current Time object
      utc_timestamp = Time.now.utc

      # Convert to integer with milliseconds:  (Seconds since Epoch * 1000) + (6-digit microsecond fraction / 1000)
      utc_timestamp_with_ms_int = (utc_timestamp.tv_sec * 1000) + (utc_timestamp.tv_usec / 1000)

      # Format as hex, minimum of 12 digits, with leading zero.  Note that 12 hex digits handles to year 10889 (*).
      utc_timestamp_with_ms_hexstring = "%012x" % utc_timestamp_with_ms_int

      # If we supply UUIDTOOLS with a MAC address, it will use that rather than retrieving from system.
      # Use a regular expression to split into array, then insert ":" characters so it "looks" like a MAC address.
      UUIDTools::UUID.mac_address = (utc_timestamp_with_ms_hexstring.scan /.{2}/).join(":")

      # Generate Version 1 UUID (see RFC 4122).
      comb_guid = UUIDTools::UUID.timestamp_create().to_s 

      # Assign generted COMBination GUID to .id
      self.id = comb_guid

      # (*) A note on maximum time handled by 6-byte timestamp that includes milliseconds:
      # If utc_timestamp_with_ms_hexstring = "FFFFFFFFFFFF" (12 F's), then 
      # Time.at(Float(utc_timestamp_with_ms_hexstring.hex)/1000).utc.iso8601(10) = "10889-08-02T05:31:50.6550292968Z".
    end
  end
end

1 Ответ

4 голосов
/ 19 января 2012
  • Пострадает ли PostgreSQL от фрагментации индекса, когда PRIMARY KEY имеет тип UUID?

Да, это следовало ожидать.Но если вы собираетесь использовать стратегию COMB, этого не произойдет.Ряды будут всегда в порядке (это не совсем верно, но потерпите меня).

Кроме того, производительность между собственным pgsql UUID и VARCHAR не так уж отличается .Еще один момент для рассмотрения.

  • Можно ли избежать фрагментации, если младшие 6 байтов GUID являются последовательными?

В моем тесте I 'мы обнаружили, что UUID1 (RFC 4122) является последовательным, в сгенерированный uuid уже добавлена ​​временная метка.Но да, добавление метки времени в последние 6 байтов подтвердит этот порядок.Это то, что я сделал в любом случае, потому что, очевидно, временная метка уже не является гарантией порядка.Подробнее о COMB здесь

  • Является ли COMB GUID, реализованный ниже, приемлемым и надежным способом создания последовательных GUID в Rails?

Я не использую рельсы, но я покажу вам, как я это сделал в django:

import uuid, time

def uuid1_comb(obj):
    return uuid.uuid1(node=int(time.time() * 1000))

Где node - 48-разрядное положительное целое число, идентифицирующее аппаратный адрес.

Что касается вашей реализации, то одним из основных преимуществ использования uuid является то, что вы можете безопасно генерировать их вне базы данных, поэтому использование вспомогательного класса является одним из допустимых способов сделать это.Вы всегда можете использовать внешний сервис для генерации uuid, такой как снежинка , но это может быть преждевременной оптимизацией на этом этапе.

...