企業がビッグデータ分析プラットフォームを使用する場合、最初に大量のデータを複数のデータソースからビッグデータプラットフォームに移行する必要があります。
データをインポートする前に、DolphinDBデータベースの基本的な概念と機能を理解する必要があります。
DolphinDBデータテーブルは、ストレージメディアに応じて3つのタイプに分類されます。
- メモリテーブル:データはこのノードのメモリにのみ保存され、アクセス速度は最速ですが、ノードがシャットダウンされるとデータは失われます。
- ローカルディスクテーブル:データはローカルディスクに保存されます。ノードが再起動しても、スクリプトを使用してデータをメモリに簡単にロードできます。
- 分散テーブル:データは物理的に異なるノードに分散されます。DolphinDBの分散コンピューティングエンジンを介して、論理的には、ローカルテーブルのように均一に検索できます。
DolphinDBデータテーブルは、パーティション化されているかどうかに応じて2つのタイプに分けられます。
- 普通のテーブル
- パーティションテーブル
従来のデータベースでは、パーティショニングはデータテーブル用です。つまり、同じデータベース内の各データテーブルは異なるパーティショニングスキームを持つことができます。一方、DolphinDBのパーティショニングはデータベース用です。つまり、データベースは1つのパーティショニングスキームしか使用できません。2つのテーブルのパーティションスキームが異なる場合、それらを同じデータベースに配置することはできません。
DolphinDBは、3つの柔軟なデータインポート方法を提供します。
- CSVテキストファイルを介してインポート
- HDF5ファイルを介してインポート
- ODBC経由でインポート
1.CSVテキストファイルを介してインポート
CSVファイルを介したデータ転送は、より一般的なデータ移行方法です。DolphinDBには、CSVファイルをインポートするための3つの関数loadText、ploadText、およびloadTextExが用意されています。以下では、サンプルのCSVファイルcandle_201801.csvを使用して、これら3つの関数の使用法を説明します。
1.1 loadText
構文:loadText(filename、[delimiter = '、']、[schema])
パラメータ:
filenameはファイル名です。
区切り文字とスキーマはどちらもオプションのパラメータです。
delimiterは、さまざまなフィールドの区切り文字を指定するために使用されます。デフォルトは「、」です。
スキーマは、データのインポート後に各フィールドのデータタイプに使用されます。これはテーブルタイプです。DolphinDBはフィールドタイプの自動認識機能を提供しますが、システムによって自動的に認識されるデータタイプが要件を満たさない場合があります。たとえば、サンプルのCSV Candle_201801.csvをインポートすると、ボリュームフィールドはINTタイプとして認識されます。実際、LONGタイプが必要です。 、次に、スキーマパラメータを使用する必要があります。
スキーマテーブルを作成するスクリプト:
nameCol = `symbol`exchange`cycle`tradingDay`date`time`open`high`low`close`volume`turnover`unixTime typeCol = [SYMBOL、SYMBOL、INT、DATE、DATE、INT、DOUBLE、DOUBLE、DOUBLE、DOUBLE 、INT、DOUBLE、LONG] schemaTb = table(nameCol as name、typeCol as type)
テーブルに多くのフィールドがある場合、スキーマテーブルを作成するためのスクリプトは非常に冗長になります。この問題を回避するために、DolphinDBはextractTextSchema関数を提供します。この関数は、テキストファイルからテーブルの構造を抽出できます。指定する必要があるフィールドタイプを変更するだけで済みます。
dataFilePath = "/home/data/candle_201801.csv" schemaTb = extractTextSchema(dataFilePath) update schemaTb set type = `LONG where name =` volume tt = loadText(dataFilePath ,, schemaTb)
1.2 ploadText
ploadTextは、データファイルをパーティションテーブルとしてメモリに並列にロードします。構文はloadTextとまったく同じですが、ploadTextの方が高速です。ploadTextは、主に大きなファイルをすばやくロードするために使用されます。複数のコアを最大限に活用してファイルを並列にロードするように設計されています。並列処理の程度は、サーバー自体のコア数とノードのlocalExecutors構成によって異なります。
以下では、loadTextとploadTextのパフォーマンスを比較します。
まず、スクリプトを使用して4GCSVファイルを生成します。
filePath = "/home/data/testFile.csv" appendRows = 100000000 dateRange = 2010.01.01..2018.12.30 ints = rand(100、appendRows) symbols = take(string( 'A' .. 'Z')、appendRows ) dates = take(dateRange、appendRows) floats = rand(float(100)、appendRows) times = 00:00:00.000 + rand(86400000、appendRows) t = table(ints as int、symbols as symbol、dates as date、 float as float、times as time) t.saveText(filePath)
loadTextとploadTextを使用して、それぞれファイルをインポートします。このノードは4コアの8スレッドCPUです。
タイマーloadText(filePath); //経過時間:39728.393ミリ秒 timer ploadText(filePath); //経過時間:10685.838ミリ秒
結果は、ploadTextのパフォーマンスがloadTextのほぼ4倍であることを示しています。
1.3 loadTextEx
语法:loadTextEx(dbHandle、tableName、[partitionColumns]、fileName、[delimiter = '、']、[schema])
パラメータ:
dbHandleはデータベースハンドルです。
tableNameは、データを保持する分散テーブルの名前です。
partitionColumns、delimiter、schemaはオプションのパラメータです。
パーティション化スキームがシーケンシャルパーティションでない場合は、partitionColumnsをするために指定する必要があるパーティション列を示しています。
fileNameは、インポートされたファイルの名前を表します。
delimiterは、さまざまなフィールドの区切り文字を指定するために使用されます。デフォルトは「、」です。
スキーマは、データのインポート後に各フィールドのデータタイプに使用されます。これはテーブルタイプです。
loadText関数は、常にデータをメモリにインポートします。データファイルが非常に大きい場合、作業マシンのメモリが簡単にボトルネックになる可能性があります。loadTextExはこの問題をうまく解決できます。すべてのメモリをインポートしてから保存するのではなく、インポートして保存することにより、静的CSVファイルをDolphinDB分散テーブルとして比較的スムーズなデータフローモードで保存します。パーティションテーブル方式は、メモリ使用量の要件を大幅に削減します。
まず、データを格納するための分散テーブルを作成します。
dataFilePath = "/home/data/candle_201801.csv" tb = loadText(dataFilePath) db = database( "dfs:// dataImportCSVDB"、VALUE、2018.01.01..2018.01.31) db.createPartitionedTable(tb、 "cycle" 、 "tradingDay")
次に、ファイルを分散テーブルにインポートします。
loadTextEx(db、 "cycle"、 "tradingDay"、dataFilePath)
データを分析に使用する必要がある場合、パーティションメタデータは最初にloadTable関数を介してメモリにロードされます。クエリが実際に実行されると、DolphinDBはオンデマンドでデータをメモリにロードします。
tb = database( "dfs:// dataImportCSVDB")。loadTable( "cycle")
2.HDF5ファイルを介してインポートします
HDF5は、CSVよりも効率的なバイナリデータファイル形式であり、データ分析の分野で広く使用されています。DolphinDBは、HDF5形式のファイルを介したデータのインポートもサポートしています。
DolphinDBは、HDF5プラグインを使用してHDF5ファイルにアクセスします。プラグインは次の方法を提供します。
- hdf5 :: ls:h5ファイル内のすべてのグループオブジェクトとデータセットオブジェクトを一覧表示します。
- hdf5 :: lsTable:h5ファイル内のすべてのデータセットオブジェクトを一覧表示します。
- hdf5 :: hdf5DS:h5ファイル内のデータセットのメタデータを返します。
- hdf5 :: loadHdf5:h5ファイルをメモリテーブルにインポートします。
- hdf5 :: loadHdf5Ex:h5ファイルをパーティションテーブルにインポートします。
- hdf5 :: extractHdf5Schema:h5ファイルからテーブル構造を抽出します。
プラグインメソッドを呼び出すときは、loadHdf5を呼び出すときにhdf5 :: loadHdf5など、メソッドの前に名前を指定する必要があります。呼び出すたびに名前を使用したくない場合は、useキーワードを使用できます。
hdf5 loadHdf5(filePath、tableName)を使用します
DolphinDBプラグインを使用するには、最初にHDF5プラグインをダウンロードしてから、ノードのpluginsディレクトリにプラグインをデプロイする必要があります。使用する前にプラグインをロードします。次のスクリプトを使用します。
loadPlugin( "plugins / hdf5 / PluginHdf5.txt")
HDF5ファイルのインポートはCSVファイルに似ています。たとえば、データセット:candle_201801を含むサンプルHDF5ファイルcandle_201801.h5をインポートする場合、インポートする最も簡単な方法は次のとおりです。
dataFilePath = "/home/data/candle_201801.h5" datadataName = "candle_201801" tmpTB = hdf5 :: loadHdf5(dataFilePath、datasetName)
インポートするデータタイプを指定する必要がある場合は、hdf5 :: extractHdf5Schemaを使用できます。スクリプトは次のとおりです。
dataFilePath = "/home/data/candle_201801.h5" datadataName = "candle_201801" schema = hdf5 :: extractHdf5Schema(dataFilePath、datasetName) update schema set type = `LONG where name =` volume tt = hdf5 :: loadHdf5(dataFilePath、datasetName 、スキーマ)
HDF5ファイルが非常に大きく、作業マシンのメモリがフルロードをサポートできない場合は、hdf5 :: loadHdf5Exを使用してデータをロードできます。
まず、データを格納するための分散テーブルを作成します。
dataFilePath = "/home/data/candle_201801.h5" datadataName = "candle_201801" dfsPath = "dfs:// dataImportHDF5DB" tb = hdf5 :: loadHdf5(dataFilePath、datasetName) db = database(dfsPath、VALUE、2018.01.01。。 2018.01.31) db.createPartitionedTable(tb、 "cycle"、 "tradingDay")
次に、hdf5 :: loadHdf5Ex関数を使用してHDF5ファイルをインポートします。
hdf5 :: loadHdf5Ex(db、 "cycle"、 "tradingDay"、dataFilePath、datasetName)
3.ODBCインターフェースを介してインポートします
DolphinDBは、サードパーティのデータベースに接続し、データベースからDolphinDBメモリデータテーブルにテーブルを直接読み取るためのODBCインターフェイスをサポートしています。DolphinDBが提供するODBCプラグインを使用して、ODBCがサポートするデータベースからDolphinDBにデータを簡単に移行します。
ODBCプラグインは、サードパーティのデータソースからのデータを操作するための次の4つの方法を提供します。
- odbc :: connect:接続を開きます。
- odbc :: close:接続を閉じます。
- odbc :: query:指定されたSQLステートメントに従ってデータをクエリし、それをDolphinDBメモリテーブルに返します。
- odbc :: execute:データを返さずに、サードパーティのデータベースで指定されたSQLステートメントを実行します。
ODBCプラグインを使用する前に、ODBCドライバーをインストールする必要があります。ODBCプラグインのチュートリアルを参照してください。
次に、例としてSQL Serverに接続します。既存のデータベースの特定の構成は、次のとおりです。
サーバー:172.18.0.15
デフォルトのポート:1433
接続ユーザー名:sa
パスワード:123456
データベース名:SZ_TAQ
データベーステーブルは2016年1月1日のデータを選択し、テーブル名はcandle_201801であり、フィールドはCSVファイルと同じです。
ODBCプラグインを使用してSQLServerデータベースに接続するには、最初のステップとして、プラグインをダウンロードし、plugins \ odbcディレクトリ内のすべてのファイルを解凍してDolphinDBServerのplugins / odbcディレクトリにコピーし、次のスクリプトを使用してプラグインの初期化を完了します。
//再入插件 負荷プラグイン( "plugins / odbc / odbc.cfg") // η接SQL Server conn = odbc :: connect( "Driver = ODBC Driver 17 for SQL Server; Server = 172.18.0.15; Database = SZ_TAQ; Uid = sa; Pwd = 123456; ")
データをインポートする前に、分散ディスクデータベースを作成してデータを保存します。
// DolphinDBインポートテーブルのテンプレートとしてSQLServerからテーブル構造を取得します tb = odbc :: query(conn、 "select top 1 * from Candle_201801") db = database( "dfs:// dataImportODBC"、VALUE、2018.01。 01..2018.01.31) db.createPartitionedTable(tb、 "cycle"、 "tradingDay")
SQL Serverからデータをインポートし、DolphinDBパーティションテーブルとして保存します。
data = odbc :: query(conn、 "select * from Candle_201801") tb = database( "dfs:// dataImportODBC")。loadTable( "cycle") tb.append!(data);
ODBCを介してデータをインポートすると、ファイルのエクスポートとインポートのプロセスが回避され、DolphinDBのタイミング操作メカニズムを介して、タイミングデータのタイミング同期のデータチャネルとしても使用できます。
4.財務データのインポートケース
以下は、証券市場の日次K線チャートデータファイルのインポート例です。データはCSVファイル形式でディスクに保存されます。年間サブディレクトリに従って合計10年のデータが保存されます。合計約100Gのデータ。パスの例は次のとおりです。
2008年
---- 000001.csv
---- 000002.csv
---- 000003.csv
---- 000004.csv
----..。
2009年
..。
2018年
図に示すように、各ファイルの構造は一貫しています。
4.1ゾーニング計画
データをインポートする前に、まずデータのパーティションを計画する必要があります。これには、次の2つの考慮事項が含まれます。
- パーティションフィールドを決定します。
- パーティションの粒度を決定します。
まず、毎日のクエリステートメントの実行頻度に応じて、トレーディングフィールドとシンボル2フィールドを組み合わせた範囲(RANGE)のパーティション化に使用します。一般的に使用される検索フィールドをパーティション化することで、データの取得と分析の効率を大幅に向上させることができます。
次に行うことは、2つのパーティションの粒度をそれぞれ定義することです。
既存のデータの期間は2008年から2018年であるため、データは年ごとに時間で分割されます。時間分割を計画するときは、後続の受信データ用に十分なスペースを残すことを考慮する必要があるため、ここでは時間範囲を次のように設定します。 2008-2030。
yearRange = date(2008.01M + 12 * 0..22)
ここには何千ものストックコードがあります。ストックコードが値(VALUE)でパーティション化されている場合、各パーティションのサイズはわずか数メガバイトですが、パーティションの数は多くなります。分散システムは、クエリを実行するときに、クエリステートメントを複数のサブタスクに分割し、それらを異なるパーティションに分散して実行します。したがって、値によるパーティション分割方法では、タスクの数が非常に多くなり、タスクの実行時間が非常に短くなり、システムが管理タスクに費やすことになります。代わりに、時間はタスク自体の実行時間よりも長くなります。このような分割方法は明らかに不合理です。ここでは、すべてのストックコードを範囲に応じて100の間隔に分割し、各間隔をパーティションとして使用します。最終的なパーティションサイズは約100Mです。新しい在庫データが後で来ることを考慮して、仮想コード999999が追加され、最後の在庫コードとのパーティションを形成して、後続の新しい在庫のデータを保存します。
次のスクリプトを使用して、シンボルフィールドのパーティション範囲を取得します。
//すべての年次ディレクトリをトラバースし、ストックコードリストを分類し、cutPoint シンボル= array(SYMBOL、 0、100)を介して100間隔に分割しますyearDirs = files(rootDir)[`filename] for(yearDir in yearDirs){ path = rootDir + "/" + yearDir symbol.append!(files(path)[`filename] .upper()。strReplace("。CSV "、" ")) } //重複を削除し、拡張スペースを増やします: symbols = symbols.distinct()。sort!()。append!( "999999"); // 100の部分に 均等に分割symRanges = symbol.cutPoints(100) 次のスクリプトを使用して、2次元の組み合わせ(COMPO)パーティションを定義し、データベースを作成して、パーティションテーブル: columns = `symbol`exchange`cycle`tradingDay`date`time`open`high`low`close`volume`turnover`unixTime types = [SYMBOL、SYMBOL、INT、DATE、DATE、TIME、DOUBLE、DOUBLE、 DOUBLE、DOUBLE、LONG、DOUBLE、LONG] dbDate = database( ""、RANGE、yearRange) dbID = database( ""、RANGE、symRanges) db = database(dbPath、COMPO、[dbDate、dbID]) pt = db.createPartitionedTable(table(1000000:0、columns、types)、tableName、 `tradingDay`symbol)
パーティションはデータを格納するDolphinDBの最小単位であり、DolphinDBはパーティションに排他的に書き込むことに注意してください。タスクが並行して実行される場合、複数のタスクが同時にデータをパーティションに書き込むことは避ける必要があります。この場合、毎年のデータは別々のタスクに渡され、各タスク操作のデータ境界は重複しないため、同じパーティションに複数のタスクを書き込むことはできません。
4.2データのインポート
データインポートスクリプトの主なアイデアは非常に単純です。ディレクトリツリーをループすることにより、すべてのCSVファイルを1つずつ分散データベーステーブルdfs:// SAMPLE_TRDDBに読み書きします。ただし、特定のインポートプロセスにはまだ多くの詳細があります。
最初に発生する問題は、CSVファイルに保存されているデータ形式が、時間フィールドなど、DolphinDBの内部データ形式と異なることです。ファイル内の「9390100000」は、ミリ秒単位の正確な時間を示します。直接読み取ると、数値タイプとして認識されます。 、時間タイプの代わりに、ここでは、データ変換関数datetimeParseをフォーマット関数形式と組み合わせて使用して、データのインポート時に変換する必要があります。キースクリプトは次のとおりです。
datetimeParse(format(time、 "000000000")、 "HHmmssSSS")
循環インポートによる実装は非常に簡単ですが、実際には100Gデータは約5Mの非常に多数の小さなファイルで構成されています。シングルスレッド操作で長時間待機する場合は、クラスターのリソースを最大限に活用するために、年ごとにデータをインポートして分割します。複数のサブタスクに分割し、それらを各ノードのタスクキューに順番に送信して並行して実行し、インポートの効率を向上させます。このプロセスは、次の2つのステップで実装されます。
(1)カスタム関数を定義します。関数の主な機能は、指定された年次ディレクトリ内のすべてのファイルをインポートすることです。
//循環目理開始目录下的所有数データ件 defloadCsvFromYearPath(path、dbPath、tableName){ symbols = files(path)[`filename] for(sym in symbol){ filePath = path +" / "+ sym t = loadText (filePath) database(dbPath).loadTable(tableName).append!(select symbol、exchange、cycle、tradingDay、date、datetimeParse(format(time、 "000000000")、 "HHmmssSSS")、open、high、low、close 、volume、turnover、unixTime from t) } }
(2)submitJob関数と組み合わせたrpc関数を介して実行するために、上記で定義された関数を各ノードに送信します。
nodesAlias = "NODE" + string (1..4)years = files(rootDir)[`filename] index = 0; for(year in years){ yearPath = rootDir + "/" + year des = "loadCsv_" + year rpc(nodesAlias [index%nodesAlias.size()]、submitJob、des、des、loadCsvFromYearPath、yearPath、dbPath、tableName) index = index + 1 }
データのインポートプロセス中に、pnodeRun(getRecentJobs)を使用して、バックグラウンドタスクの完了を監視できます。
ケース完了スクリプト