Unknown Region

プログラムでハマったエラーとその解決方法についての備忘録メモ

【Python】pandasでCSVファイルをParquetファイルへ変換する

備忘録がてらメモ。

最近、ビッグデータを扱うプロジェクトでは「Parquet」という列指向フォーマット(カラムナフォーマット)が好まれるようになってきた。

parquet.apache.org

Parquetの利点は、なんといってもデータの読み込みの速さとストレージ容量の節約にあるだろう。

今回はPythonpandasを利用して、CSV形式のファイルを読みこんでParquet形式のファイルに変換する簡単な方法をメモしておく。

 

今回は、次のようなCSVファイルを用意した。

例: data.csv

"id","name","rating","birthday","registered_at"
"1","ラオウ","5.1","1990-01-01","2023-01-01 01:01:01"
"2","トキ","4.2","1991-01-02","2023-01-02 02:02:02"
"3","ジャッカル","3.3","1992-01-03","2023-01-03 03:03:03"
"4","ケンシロウ","2.4","1993-01-04","2023-01-04 04:04:04"

UTF-8形式で保存する前提

 

そして以下のようなPythonのプログラムで変換をかける

例: csv_to_parquet.py

import pandas as pd
from datetime import datetime

dtype = {
    'id': 'long',
    'rating': 'double',
    'birthday': 'str',
    'registered_at': 'str'
};
parse_dates = [3, 4] # timestampとして利用するカラムを指定する
df = pd.read_csv("data.csv", encoding='utf-8', dtype = dtype, parse_dates = parse_dates)
df.to_parquet("data.parquet")

注意すべきは[parse_dates]というパラメータで、ここにはtimestampとして利用したいカラムを指定するということ。

これによってCSV自体を読み込む際は[dtype][str]を指定しておき、その後timestampに変換をかけるような動きとなる。

逆にtimestampへの変換を行わずに文字列として保存したい場合は、この[parse_dates]を指定しなければOK。

 

 

 

出力が終わったら[parquet-cli]等で内容を確認する

github.com

例: 確認コマンド結果 (スキーマ)

$parquet schema data.parquet
{
  "type" : "record",
  "name" : "schema",
  "fields" : [ {
    "name" : "id",
    "type" : [ "null", "long" ],
    "default" : null
  }, {
    "name" : "name",
    "type" : [ "null", "string" ],
    "default" : null
  }, {
    "name" : "rating",
    "type" : [ "null", "double" ],
    "default" : null
  }, {
    "name" : "birthday",
    "type" : [ "null", {
      "type" : "long",
      "logicalType" : "timestamp-micros"
    } ],
    "default" : null
  }, {
    "name" : "registered_at",
    "type" : [ "null", {
      "type" : "long",
      "logicalType" : "timestamp-micros"
    } ],
    "default" : null
  } ]
}

例: 確認コマンド結果 (データ)

$parquet cat data.parquet # 内容確認
{"id": 1, "name": "ラオウ", "rating": 5.1, "birthday": 631152000000000, "registered_at": 1672534861000000}
{"id": 2, "name": "トキ", "rating": 4.2, "birthday": 662774400000000, "registered_at": 1672624922000000}
{"id": 3, "name": "ジャッカル", "rating": 3.3, "birthday": 694396800000000, "registered_at": 1672714983000000}
{"id": 4, "name": "ケンシロウ", "rating": 2.4, "birthday": 726105600000000, "registered_at": 1672805044000000}

 

ちなみに注意点としては、このtimestampの扱いがParquetのフォーマットのバージョンによって異なるということ。

例えばAWSRedshift Spectrumでは、timestampの型をint96に指定しなければ読み込まれないなどの落とし穴がある。

tkr911.hatenablog.com

尚、このint96の指定は現在は非推奨になっており、先ほど利用した[parquet-cli]の最新版では読み込みに失敗したりする。

あぁ、ややこしや、ややこしや。

 

【Java】snappy-javaで生成したsnappy圧縮ファイルが壊れる事象への対処

備忘録がてら記載。

まず[snappy-java]JavaSnappy圧縮を容易に行うためのモジュール。

github.com

この[snappy-java]を利用して生成したファイルを別システムに食わせるプログラムを作っていたのだが、受け取るプログラム側で以下のようなエラーに直面した。

[SnappyBlock: RawUncompress failed]

これはsnappy圧縮されたデータが正常に解凍できなかったということを意味している。

実は環境によって起きたり起きなかったりするので悩んでいたのだが、ようやく原因を突き止めた。

 

まずそれを説明するためには[snappy-java]の仕組みを先に説明しておく必要がある。

[snappy-java]は内部的にC++でのプログラムのコンパイルを実施する。

そしてJava側では[native]として、その生成物を実行して利用する仕組みになっている。

ただもし環境依存でC++でのコンパイルが失敗した場合には、それを利用するのを諦めて代わりにJavaで組まれたプログラムで肩代わりするという仕組みになっていた。

分かりやすく説明すると、以下の通りの構成である。

interface [SnappyApi] --implements--> SnappyNative, PureJavaSnappy

結論を言うと、問題のない環境では[SnappyNative]が、問題が起きた環境では[PureJavaSnappy]が使われていた。

問題が起きていた環境は[alpine]で、内部的には以下のエラーが発生していたのだ。

Caused by: java.lang.UnsatisfiedLinkError: /tmp/snappy-1.1.8-9651bf49-a0e3-4240-81f5-848a90902bc7-libsnappyjava.so: Error loading shared library ld-linux-x86-64.so.2: No such file or directory (needed by /tmp/snappy-1.1.8-9651bf49-a0e3-4240-81f5-848a90902bc7-libsnappyjava.so)
  at java.lang.ClassLoader$NativeLibrary.load(Native Method) ~[?:1.8.0_252]
  at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1946) ~[?:1.8.0_252]
  at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1828) ~[?:1.8.0_252]
  at java.lang.Runtime.load0(Runtime.java:809) ~[?:1.8.0_252]
  at java.lang.System.load(System.java:1088) ~[?:1.8.0_252]
  at org.xerial.snappy.SnappyLoader.loadNativeLibrary(SnappyLoader.java:198) ~[snappy-java-1.1.8.4.jar:1.1.8.4]

そこで以下の記事を元に、alpineコンパイルに必要な処置を実施した。

qiita.com

そうしたところ、無事に[SnappyNative]が利用できるようになり問題は解決した。

[snappy-java]Githubでの過去のissueのやりとりを見るに、恐らく[PureJavaSnappy]には一部スレッドセーフになっていない問題が潜んでいると思われる。

実は今回僕が作ったプログラムは非同期処理でsnappy圧縮を行うものであった。

(しかもほとんどのユーザが[SnappyNative]を利用するので、問題の報告数が少ないのではないだろうか)

 

一応参考までに、上記のjava.lang.UnsatisfiedLinkErrorを確認するためのプログラムを貼っておく。

(これをtry-catchするプログラムにして、throwableのログを出力させれば良い)

Method method = Arrays.stream(SnappyLoader.class.getDeclaredMethods())
    .filter(e -> "loadNativeLibrary".equals(e.getName()))
    .findFirst()
    .get();
method.setAccessible(true);
method.invoke(null);

 

連絡先: plugout777★yahoo.co.jp (クローラー対策のため★を@に変更してください)