MongoDBの64ビット整数

私のPHPプロジェクトでは、64ビット整数データをデータベースに保存する必要に対処する必要がありました。 このトピックに関する記事は1つしか見つかりませんでしたが、非常に詳細(場合によってはあまりにも詳細)で、すべての微妙な点を説明しています。 誰かが同様の問題に直面した場合に備えて、翻訳をHabréに公開することにしました。







私が取り組んでいる現在のプロジェクトは、キーバリューストレージと従来のRDBMSの間のブリッジであるMongoDBに基づいています。 このプロジェクトのユーザーは、Facebook UserIDによって識別されます。これは64ビット整数です。 残念ながら、 PHP用MongoDBドライバーは 32ビット整数のみをサポートしていたため、新しいFacebookユーザーで問題が発生しました。 クールな新しい長いUserIDは32ビットに切り捨てられたため、アプリケーションは正しく動作しませんでした。



MongoDBは、内部ドキュメントストレージにBSON (Binary JSON )と呼ばれるものを使用します。 BSONには、 INTと呼ばれる32ビット符号付き整数とLONGと呼ばれる64ビット符号付き整数の2つの整数数​​値型があります。 PHP用のMongoDBドライバーのドキュメントには、次のように32ビット整数型のみがサポートされていると書かれています(または、読んだ時期に応じて言われました) 「PHPは8バイト整数をサポートしていません。」 これは完全に真実ではありません。 PHPの整数型は、Cのlong型が64ビットであるプラットフォームで64ビット値をサポートします。 これは、Cのlong型が常に32ビットであるWindowsを除き、64ビットプラットフォーム(PHPが64ビットアーキテクチャ用にコンパイルされている場合)です。



PHPからMongoDBに整数が渡されるたびに、ドライバーは32ビットの最下位ビットのみを使用してドキュメントに数値を格納しました。 以下の例は、何が起こったかを示しています(64ビットプラットフォーム):



<?php $m = new Mongo(); $c = $m->selectCollection('test', 'inttest'); $c->remove(array()); $c->insert(array('number' => 1234567890123456)); $r = $c->findOne(); echo $r['number'], "\n"; ?>
      
      







表示:



  int(1015724736) 




バイナリ形式:



  1234567890123456 = 100011000101101010100111100100010101010101011000000
       1015724736 = 111100100010101010101011000000 




データのトリミングは明らかに良いアイデアではありません。 この問題を解決するには、標準のPHP整数型をMongoDBに直接渡すことを許可します。 しかし、デフォルトでMongoDBドライバーの動作を変更する代わりに、新しいmongo.native_long設定を追加しました。これは、実行中のアプリケーションが壊れる可能性があるためです。 mongo.native_long設定を有効にすると、異なるスクリプト実行結果が表示されます。



 <?php ini_set('mongo.native_long', 1); $c->insert(array('number' => 1234567890123456)); $r = $c->findOne(); var_dump($r['number']); ?>
      
      







このスクリプトは以下を表示します:



  int(1234567890123456) 




64ビットプラットフォームでは、 mongo.native_longを設定すると、64ビット整数をMongoDBに保存できます。 この場合に使用されるMongoDBデータ型は、この設定がオフになっている場合に使用されるBSON INTではなく、BSON LONGです。 この設定は、MongoDBからの読み取り時のBSON LONGデータの動作も変更します。 mongo.native_long設定が有効になっていない場合、ドライバーはすべてのBSON LONGをPHPタイプのfloatに変換します。これにより、精度が失われます。 これは次の例で確認できます。



 <?php ini_set('mongo.native_long', 1); $c->insert(array('number' => 12345678901234567)); ini_set('mongo.native_long', 0); $r = $c->findOne(); var_dump($r['number']); ?>
      
      







このスクリプトは以下を表示します:



 フロート(1.2345678901235E + 16) 




32ビットプラットフォームでは、MongoDBに整数を格納するときにmongo.native_long設定何も変更しません。以前と同様に、数値はBSON INTとして保存されます。 ただし、32ビットプラットフォームでチューニングを有効にしてMongoDBからBSON LONG番号を読み取ると、 MongoCursorExceptionがスローされ、データが正確性を失うことなく読み取れないことを警告します。



  MongoCursorException:このプラットフォームで長い1234567890123456をネイティブに表すことはできません 




設定がオフの場合、BSON LONGは、以前のドライバーの動作との後方互換性を失わないように、PHPタイプのfloatに変換されます。



mongo.native_long設定を使用すると、64ビットプラットフォームで64ビット数値を使用できますが、BSON LONG値を読み取る際のデータ損失に対する保護を除き、例外をスローすることによってのみ、32ビットプラットフォームでは何も提供しません。



PHPのMongoDBで信頼できる64ビットの数値を確保する作業の一環として、2つの新しいクラスMongoInt32MongoInt64も追加しました。 これらの2つのクラスは、数値の文字列表現の単純なラッパーです。 これらは次のように作成されます。



 <?php $int32 = new MongoInt32("32091231"); $int64 = new MongoInt64("1234567980123456"); ?>
      
      







これらのオブジェクトは、通常の番号のように、データを挿入および変更するための通常のリクエストで使用できます。



 <?php $m = new Mongo(); $c = $m->selectCollection('test', 'inttest'); $c->remove(array()); $c->insert(array( 'int32' => new MongoInt32("1234567890"), 'int64' => new MongoInt64("12345678901234567"), )); $r = $c->findOne(); var_dump($r['int32']); var_dump($r['int64']); ?>
      
      







結論:



  int(1234567890)
フロート(1.2345678901235E + 16) 




例からわかるように、データベースから値を読み取る際に何も変更はありません。 BSON INTも全体として返され、BSON LONGはfloatとして返されます。 mongo.native_long設定を有効にすると、 MongoInt64クラスに格納されたBSON LONGは、64ビットプラットフォームでは整数のPHP型と​​して返され、32ビットプラットフォームではMongoCursorExceptionが発生します。



32ビットプラットフォームでMongoDBから64ビットの数値を取得するために、別の設定mongo.long_as_objectを追加しました。 (どのプラットフォームでも)MongoDBからBSON LONGをMongoInt64オブジェクトとして返すことができます。 次のスクリプトはこれを示しています。



 <?php $m = new Mongo(); $c = $m->selectCollection('test', 'inttest'); $c->remove(array()); $c->insert(array( 'int64' => new MongoInt64("12345678901234567"), )); ini_set('mongo.long_as_object', 1); $r = $c->findOne(); var_dump($r['int64']); echo $r['int64'], "\n"; echo $r['int64']->value, "\n"; ?>
      
      







スクリプト出力:



 オブジェクト(MongoInt64)#7(1){
   ["値"] =>
  文字列(17) "12345678901234567"
 }
 12345678901234567
 12345678901234567 




MongoInt32クラスMongoInt64クラス__toString()メソッドを実装しているため、値をエコー経由で出力できます。 値は文字列としてのみ取得できます。 MongoDBはタイプに敏感であり、文字列に含まれる数字を数字として受け入れないことに注意してください。 このスクリプトはこれを示します(64ビットプラットフォーム):



 <?php ini_set('mongo.native_long', 1); $m = new Mongo(); $c = $m->selectCollection('test', 'inttest'); $c->remove(array()); $nr = "12345678901234567"; $c->insert(array('int64' => new MongoInt64($nr))); $r = $c->findOne(array('int64' => $nr)); // $nr is a string here var_dump($r['int64']); $r = $c->findOne(array('int64' => (int) $nr)); var_dump($r['int64']); ?>
      
      







結論:



 ヌル
 int(12345678901234567) 




以下の表は、含まれる設定に応じて、さまざまな数値変換がすべてどのように機能するかを示しています。



PHP-> 32ビットプラットフォームのMongoDB



初期値 native_long = 0 native_long = 1
1234567 INT(1234567) INT(1234567)
123456789012 フロート(123456789012) フロート(123456789012)
MongoInt32( "1234567") INT(1234567) INT(1234567)
MongoInt64( "123456789012") ロング(123456789012) ロング(123456789012)




PHP-> 64ビットプラットフォームでのMongoDB



初期値 native_long = 0 native_long = 1
1234567 INT(1234567) ロング(1234567)
123456789012 ごみ ロング(123456789012)
MongoInt32( "1234567") INT(1234567) INT(1234567)
MongoInt64( "123456789012") ロング(123456789012) ロング(123456789012)




MongoDB-> 32ビットプラットフォーム上のPHP



MongoDBで long_as_object = 0、native_long = 0 long_as_object = 0、native_long = 1 long_as_object = 1
INT(1234567) int(1234567) int(1234567) int(1234567)
ロング(123456789012) フロート(123456789012) MongoCursorException MongoInt64( "123456789012")




MongoDB-> 64ビットプラットフォーム上のPHP



MongoDBで long_as_object = 0、native_long = 0 long_as_object = 0、native_long = 1 long_as_object = 1
INT(1234567) int(1234567) int(1234567) int(1234567)
ロング(123456789012) フロート(123456789012) int(123456789012) MongoInt64( "123456789012")




おわりに



気づいたように、MongoDBを使用してPHPで64ビット整数のサポートを取得するのは簡単なことではありません。 コードで64ビットプラットフォームのみを使用する場合は、 mongo.native_long = 1を使用することをお勧めします。 この場合、データベースに書き込むすべての整数は、64ビットであっても元の形式の整数と同様にそこから返されます。



32ビットプラットフォームで作業する必要がある場合(これにはWindows用の64ビットPHPビルドが含まれます!)、PHPで標準の整数型を使用して64ビット数を格納することはできません。MongoInt64クラスを使用する必要があります。数字の文字列表現を使用します。 また、MongoDBコンソールはすべての数値を浮動小数点数と見なし、64ビット整数を表示できないことにも留意する必要があります。 代わりに、それらをフロートとして表示します。 コンソールでこれらの番号を変更しようとしないでください。変更すると、タイプが変更されます。



たとえば、スクリプトを実行した後:



 <?php $m = new Mongo(); $c = $m->selectCollection('test', 'inttest'); $c->remove(array()); $c->insert(array('int64' => new MongoInt64("123456789012345678")));
      
      







MongoDB( mongo )コンソールは次のように動作します:



  $モンゴ
 MongoDBシェルバージョン:1.4.4
 url:テスト
接続先:テスト
 「help」と入力するとヘルプが表示されます
 >テストを使用する
 dbテストに切り替え
 > db.inttest.find()
 {「_id」:ObjectId(「4c5ea6d59a14ce1319000000」)、「int64」:{「floatApprox」:123456789012345680、「top」:28744523、「bottom」:2788225870}} 




もちろん、64ビット整数をサポートするドライバーを使用してデータを読み取ると、正しい結果が得られます。



 ini_set('mongo.long_as_object', 1); $r = $c->findOne(); var_dump($r['int64']); ?>
      
      







表示されます:



 オブジェクト(MongoInt64)#7(1){
   ["値"] =>
  文字列(18) "123456789012345678"
 } 




この記事で説明する新しい機能は、mongo 1.0.9リリースの一部でありpecl install mongoコマンドを使用してPECLから利用できます。

64ビット整数で頑張ってください!



PSこれは私の最初の翻訳です、あまり蹴らないでください:)



All Articles