Skip to content

MS_LargeDataProcessing1

nishi_74322014 edited this page Aug 18, 2026 · 1 revision

大量データの処理方式1

概要

どの OS も(Windows も Linux も)

  • ファイル編成法 - Wikipedia
    https://ja.wikipedia.org/wiki/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E7%B7%A8%E6%88%90%E6%B3%95

    • 汎用コンピュータ(メインフレーム) や一部のオフコンの専用 OS では、
      各ファイル(データセット) 内の、レコードの属性を定義する。
      (固定長/可変長/非定型、固定長の場合のレコード長、格納・検索方法など)

    • なお、いわゆるオープンシステムではバイト ストリームが基本であり、
      ファイル編成法は存在しない
      (詳細は「レガシーシステムとオープンシステムの比較」を参照)。

と、バイト・ストリームによる

程度はサポートしています。

本ページでは、

  • これら(ファイルシステムのバイト・ストリーム)を
    使用しての、大量データの処理方式を考えます。

  • なお、メモリに保持可能なデータ量の場合の設計ディシジョンについては考慮しません。
    この場合、メモリの大量消費による同時実行性の低下や CPU 時間が問題となります。

アクセス方式とレコード長

SAM(順次アクセス方式) - シーケンシャル・アクセス

  • SAM(順次アクセス方式) - シーケンシャル・アクセスを
    実現するにあたっては、特に問題となる点はありません。

  • READ や WRITE の API によって自動的に
    ファイル・ポインタ(読み書きの開始点)が移動されます。

DAM(直接アクセス方式) - ランダム・アクセス

DAM(直接アクセス方式) - ランダム・アクセスを実現するにあたっては、
特にファイル・ポインタ(読み書きの開始点)の制御が問題となります。

固定長

固定長レコードの場合、

  • ファイル・ポインタ(読み書きの開始点)の制御が容易です。

  • Windows では、SetFilePointer 関数を使用して、
    ファイル・ポインタ(読み書きの開始点)を、全て自分で制御して
    ランダム・アクセスを実現する必要があります。

  • 注意点

    • 4GB(32 ビット)以上のファイルを扱う時は、
      上位 32 ビットと、下位 32 ビットを 64 ビット データ型に
      変換する必要があることです。

    • このため、レガシー VB では Seek ステートメントで 2GB 以上の
      ファイル・ポインタを指定することができないという制約があります。
      4GB(32 ビット)でないのは、signed の型を使用しているためと思われます。

    • この制約は、Java にもあるようです。

    • .NET では、FileStream.Seek メソッドの引数が
      64bit(long 型) 対応されたため、この問題は発生しなくなったようです。

余談になりますが、.NET で固定長レコードを処理する場合に便利な
C 構造体によるバッファ型抜きを .NET 構造体と Marshal クラスを使用して
実現できます。

なお、このバッファ型抜き処理は、Open棟梁の共通部品に実装されています。

補足(最新化:Marshal を使わない型抜き): 現在の .NET では、
Span<byte>MemoryMarshal を使うと
アンマネージ メモリへのコピーなしで構造体として読み出せる。

[StructLayout(LayoutKind.Sequential, Pack = 1)]
readonly struct Record { public readonly int Id; public readonly long Amount; }

Span<byte> buf = stackalloc byte[Unsafe.SizeOf<Record>()];
stream.ReadExactly(buf);
Record r = MemoryMarshal.Read<Record>(buf);

固定長レコードの高速処理では、
ArrayPool<byte> でバッファを使い回すことと合わせて、
GC 圧を大幅に下げられる
ただし、エンディアンと文字コード(Encoding.GetString)の扱いは
別途明示的に行う必要がある。

可変長

可変長レコードの場合、

  • ファイル・ポインタ(読み書きの開始点)の制御が困難です。

  • 理由は、簡単でレコード長が可変のため n 番目のレコードの
    ファイル・ポインタ(読み書きの開始点)を算出できないためです。

  • 対応方法については、いくつか方法が考えられます。

    • SAM(順次アクセス方式) - シーケンシャル・アクセスで、
      事前に、全てのデータを読み取っておき、これにより、
      全てのレコードの開始位置を配列などに記憶しておく。

      • 初回の開始位置の配列作成の読み込みに I/O 時間がかかってしまう。
    • 全てのレコードを読み取ることが困難な場合でも、
      レコード番号などがデータ中に含まれるようなら
      ファイル・ポインタ(読み書きの開始点)を推測して、
      効率良くレコードにアクセスする事を試みるのは可能。

      • 上手いアルゴリズムを考えだすのが難しい。

      • レコード番号などがデータ中に含まれる必要がある
        カスタム仕様であるため、汎用品は存在しない。

移行メモ(補足): 元ページの「n番目のレコードのファイル・ポインタが
できないため」は文意が欠けているため、
を算出できないため」と補って掲載した。

補足(インデックス ファイルという定石): 「開始位置を配列に記憶する」方式は、
実質的に自前でインデックスを作ることに他ならない。
これを永続化して別ファイル(インデックス ファイル)に持つのが定石で、
初回のフル スキャンを 1 度だけに抑えられる。
メインフレームの ISAM/VSAM や、RDBMS の
SQL Server のインデックスと発想は同じである。

  • CSV の場合

    • 可変長レコードの主要なフォーマットには CSV があります。

    • CSV の問題は、CSV のフォーマットの仕様によってパーサが複雑になる事です。

    • 例えば、データ内に改行コードなどが含まれる場合は、
      前述のファイル・ポインタ(読み書きの開始点)を推測して、
      レコードにアクセスする等の方式の採用は困難です。

      • ただし、1 フィールドの最大データ長が決まっていれば、可能と言えば可能です。
    • このため、以下のどちらの方式を採用したとしても、

      • 全てのデータを読み取っておき、全てのレコードの開始位置を配列などに記憶しておく。
      • ファイル・ポインタ(読み書きの開始点)を推測して、効率良くレコードにアクセスする。
      • FileStream.SeekPosition と、StreamReader.ReadLine を併用する。
    • 結局の所、CSV パーサーの高速化が必要になるようです。

    • .NET には、VB 2005 用の機能として、CSV パーサーが準備されているようです。
      しかし、API の仕様からも、ランダム・アクセスはサポートされていないようです。

移行メモ(誤字): 元ページの「API の使用からも」は
「API の仕様からも」の誤記。

補足(FileStream.PositionStreamReader の併用は危険): 本文の
「※ FileStream.SeekPosition と、StreamReader.ReadLine を併用する」は、
そのままでは正しく動作しない点に注意。
StreamReader は内部でバッファリングしているため、
ReadLine で 1 行読んだ時点の FileStream.Position
その行の終端ではなく、先読みしたバッファの終端を指す。

位置を正確に記録したい場合は、

  • StreamReader を使わず FileStream を自分で読み進める
  • あるいは読み取ったバイト数を自前で積算する

必要がある。

なお、現在の .NET で CSV を扱うなら
CsvHelperSep といったライブラリを使うのが一般的で、
後者は Span ベースで極めて高速である。

  • その他

    • ファイル編成法のないオープンシステムの
      バイト・ストリームのファイルシステムでは
      可変長レコード・ファイルのレコード更新処理は不可能。

    • 固定長レコードの処理と同じように、
      C 構造体によるバッファ型抜きで処理することが不可能。

実装上の考慮点

更新処理

固定長レコードの更新処理以外は、

  • 可変長レコードの更新処理(レコード長が変わる更新)
  • テキスト・ファイルの中間に文字やパラグラフを挿入する。
  • .etc

更新差分情報のみをメモリに保持して、
最終的に全てのデータをバイト・ストリームで書き出し直す必要がある。

このため、I/O のオーバヘッドが非常に大きくなってしまいます。

補足(マージ出力という定石): 全件を書き出し直すこと自体は避けられないが、
**入力を読みながら出力へ書き出す(ストリーミング)**形にすれば、
メモリ消費は一定に保てる。

  1. 入力ファイルを先頭から順に読む
  2. 更新対象なら差分を適用して出力、そうでなければそのまま出力
  3. 完了後、出力ファイルを入力ファイルと差し替える

これはメインフレームのマスタ更新(旧マスタ + トランザクション → 新マスタ)
そのものであり、現在も大量バッチの基本形である。
差し替えを原子的に行うには File.Replace を使う。

MMFの使用ポイント

メモリマップト ファイル(MMF)を使用すれば、

  • マップ ビューのマップ・アンマップによるランダム・アクセス
  • マップ ビューの範囲でオンメモリ処理(I/O 回数の軽減)

が可能になり、効率的に参照処理を
実装できるようになる可能性があります。

補足: .NET では System.IO.MemoryMappedFiles.MemoryMappedFile
利用できる。
大きなファイルへのランダム アクセスが多い場合に有効だが、
32bit プロセスではアドレス空間の制約で大きなビューを取れない
WOW64)ため、64bit 前提の技法である。

余談

COBOL+VSAM

  • COBOL などは、RDBMS が無い時代に、ホスト・UNIX の OS に実装される
    VSAM を内部的に使用して業務アプリケーションを開発することを
    目的としていたため、この仕組みに適合した言語仕様になっている。

  • このため可変長のランダム・アクセスであっても
    実装が容易で、このような問題は発生しないようです。
    このため、昔は良く「Windows はファイル・システムが弱い」と言われていました。

  • VSAM、VSAM データセット »「メインフレーム・コンピュータ」で遊ぼう
    http://www.arteceed.net/?p=3164

  • Virtual Storage Access Method - Wikipedia
    https://ja.wikipedia.org/wiki/Virtual_Storage_Access_Method

補足(何が違ったのか): VSAM の KSDS(キー順データセット)は、
OS が B ツリー インデックスを持つファイル編成を提供していた
つまり、現在の RDBMS が担っている役割の一部を
ファイル システム層が受け持っていたということである。

オープン システムがこれを捨ててバイト ストリームに一本化したのは、
その役割を RDBMS に委ねたためであり、
「弱い」というより設計方針の違いと言える。
本ページ後半の「DB を使用する場合」がまさにその話である。

Unix、LinuxとWindowsのFS

Unix、Linux のファイルシステムでは断片化が発生し難いと言う話があったので、
簡単に技術的な背景を纏めているサイトをリンクしました。

DBを使用する場合

RDBMS+SQL を使用することによって、
アプリケーションはデータ・アクセスの際に

  • ファイル編成法(固定長/可変長/非定型、格納・検索方法)と
  • 上記に対応するアクセス方法(API の利用方法)

を考慮せずに、論理データに直接アクセスできるようになりました。

バッチ処理

大量データのバッチ処理は、基本的にストアドが高速です。

  • ネットワークやプロセス間の通信処理が発生しないので、
    データ送受信のラウンドトリップが発生しない。

  • カーソルによるフェッチが使用できるため
    大量データの結果セットを取得した場合も
    メモリ消費量を抑えることができる。

Java、.NET で実装する場合は、性能的に
問題が無いかを事前に検証した方が良いでしょう。

  • .NETでバッチは書けるか?(MS_DotNetBatch.md

補足: ストアド プロシージャの得失は
ストアド プロシージャにまとめてある。
なお、「カーソルによるフェッチ」は T-SQL の CURSOR を指すが、
T-SQL のカーソルは行ごとの処理で非常に遅いため、
可能な限り集合演算(セットベース)で書くのが原則である。
クライアント側で少しずつ読むという意味であれば、
DbDataReader が既にストリーミングなので同じ効果が得られる。

検索、結合、統合、集計機能

大量データのバッチ処理で、
以下の様な DBMS の機構を使用したいケースもある。

  • 検索機能(Index Seek)
  • 結合、統合、集計機能(Join、Union、Group By)

検索や結合機構にはインデックスが使用されているため

  • インデックスの構築・更新
  • 統計情報更新の構築・更新

等が必要になる。

オーバーヘッド

DB を利用可能にするまでのオーバーヘッドには以下のものがあります。

  • インポート(インサート)
  • インデックス構築・更新
  • 統計情報の構築・更新
  • 必要であればエクスポート

無償で利用できるDB

補足(最新化): 現在の選択肢は以下のとおり整理される。

現状
SQLite 第一候補。Microsoft.Data.Sqlite で .NET から利用。単一ファイル、インストール不要
SQL Server LocalDB Express の一部として提供。開発・テスト用途
SQL Server CE 開発終了。使用しない
Jet / ACE 32bit / 64bit の混在問題がある(WOW64
Docker PostgreSQL / SQL Server をコンテナで起動するのが最も手軽

補足

しかし、バッチ処理等で高性能なデータ変換処理を要求されるケースでは
未だにファイル・レベルでのデータ処理が必要になることもあります。

例:データのエクスポート → 変換処理(シーケンシャル) → データのインポート

補足: この「エクスポート → 変換 → インポート」は、まさに ETL である。
SQL Server では bcp / BULK INSERT / SSISが該当し、
一括ログ復旧モデルとの組み合わせで最小ログ記録を効かせる
SQL Server 大量データ処理時の性能問題)。
製品を使う方式はデータ連携を参照。

ETLツールを導入する方式

HULFT DataMagic などを使用することで
可変長レコードの大量データも容易に処理できる可能性があります。

  • HULFT-DataMagic 技術コラム Vol.17 【HULFT 定義一括登録編】
    技術コラム一覧 ファイル転送・データ連携 HULFT シリーズ セゾン情報システムズ
    http://www.hulft.com/column/datamagic17.html

Tags: 移行, データアクセス

NetDevInfraWiki

マイクロソフト系技術情報 Wiki
Open 棟梁 Wiki

(未着手)

開発基盤部会 Wiki

移行管理: DONETODO

Clone this wiki locally