2013-11-01 9 views
7

私はデータベースから何百万もの行を読み込み、テキストファイルに書き込もうとしています。clojure.java.jdbc /大規模な結果を遅く照会

これはdatabase dump to text file with side effects

私の問題は、今のプログラムが完了するまでのログが起こらないということのようです私の質問の続きです。私が遅れて処理していないという別の指標は、プログラムが終了するまでテキストファイルがまったく書き込まれないということです。

IRCのヒントによると、コードのclojure.java.jdbc/query領域では、:result-set-fnとデフォルトとしてdoallとする必要があるようです。

これをfor関数に置き換えようとしましたが、結果セット全体をメモリにプルするため、メモリ消費量が高いことがわかりました。

doallのようにすべてを取り込まない:result-set-fnをどうすればよいですか? -main実行が終了したら、プログラムを実行している間に徐々にログファイルを書き込んで、すべてをダンプすることはできますか?

(let [ 
      db-spec    local-postgres 
      sql     "select * from public.f_5500_sf " 
      log-report-interval 1000 
      fetch-size   100 
      field-delim   "\t"                 
      row-delim   "\n"                 
      db-connection  (doto (j/get-connection db-spec) (.setAutoCommit false)) 
      statement   (j/prepare-statement db-connection sql :fetch-size fetch-size) 
      joiner    (fn [v] (str (join field-delim v) row-delim))      
      start    (System/currentTimeMillis)            
      rate-calc   (fn [r] (float (/ r (/ (- (System/currentTimeMillis) start) 100)))) 
      row-count   (atom 0)                
      result-set-fn  (fn [rs] (lazy-seq rs)) 
      lazy-results   (rest (j/query db-connection [statement] :as-arrays? true :row-fn joiner :result-set-fn result-set-fn)) 
      ]; }}} 
     (.setAutoCommit db-connection false) 
     (info "Started dbdump session...")  
     (with-open [^java.io.Writer wrtr (io/writer "output.txt")] 
     (info "Running query...")  
     (doseq [row lazy-results] 
      (.write wrtr row) 
     )) 
     (info (format "Completed write with %d rows" @row-count)) 
    ) 

答えて

7

私はproject.cljの依存関係リストに[org.clojure/java.jdbc "0.3.0-beta1"]を置くことによってclojure.java.jdbcのための最近の修正をしました。これは、:as-arrays? trueの機能がclojure.java.jdbc/queryhereと記載されています。

これは幾分助けたと思いますが、私はまだ:result-set-fnvecに上書きできる可能性があります。

すべての行ロジックを:row-fnに挿入すると、コアの問題が解決されました。最初のOutOfMemoryの問題は、:row-fnを定義するのではなく、j/queryの結果セットを反復することと関係していました。

新(作業)のコードは以下の通りです:(限られた成功と)私は実験

(defn -main [] 
    (let [; {{{ 
     db-spec    local-postgres 
     source-sql   "select * from public.f_5500 " 
     log-report-interval 1000 
     fetch-size   1000 
     row-count   (atom 0) 
     field-delim   "\u0001" ; unlikely to be in source feed, 
             ; although i should still check in 
             ; replace-newline below (for when "\t" 
             ; is used especially) 
     row-delim   "\n" ; unless fixed-width, target doesn't 
            ; support non-printable chars for recDelim like 
     db-connection  (doto (j/get-connection db-spec) (.setAutoCommit false)) 
     statement   (j/prepare-statement db-connection source-sql :fetch-size fetch-size :concurrency :read-only) 
     start    (System/currentTimeMillis) 
     rate-calc   (fn [r] (float (/ r (/ (- (System/currentTimeMillis) start) 100)))) 
     replace-newline  (fn [s] (if (string? s) (clojure.string/replace s #"\n" " ") s)) 
     row-fn    (fn [v] 
           (swap! row-count inc) 
           (when (zero? (mod @row-count log-report-interval)) 
           (info (format "wrote %d rows" @row-count)) 
           (info (format "\trows/s %.2f" (rate-calc @row-count))) 
           (info (format "\tPercent Mem used %s " (memory-percent-used)))) 
           (str (join field-delim (doall (map #(replace-newline %) v))) row-delim)) 
     ]; }}} 
    (info "Started database table dump session...") 
    (with-open [^java.io.Writer wrtr (io/writer "./sql/output.txt")] 
     (j/query db-connection [statement] :as-arrays? true :row-fn 
       #(.write wrtr (row-fn %)))) 
    (info (format "\t\t\tCompleted with %d rows" @row-count)) 
    (info (format "\t\t\tCompleted in %s seconds" (float (/ (- (System/currentTimeMillis) start) 1000)))) 
    (info (format "\t\t\tAverage rows/s %.2f" (rate-calc @row-count))) 
    nil) 
) 

他のものは、音色のロギングを関与し、stardardをオフにします。私はREPLを使ってエディタ(vimの暖炉)に表示する前に結果をキャッシュするかもしれないと思っていましたが、それがたくさんのメモリを利用しているかどうかはわかりませんでした。

また、メモリの周りにログ部分を追加しました。(.freeMemory (java.lang.Runtime/getRuntime))です。私はVisualVMに精通しておらず、問題がどこにあるのか正確に特定していませんでした。

私は今、どのように動作しているのか、あなたのお手伝いをしてくれてありがとうございます。

3

:fetch-sizeオプションでprepare-statementを使用できます。それ以外の場合、結果はレイジーシーケンスで配信されますが、クエリ自体は熱心です。

prepare-statementには接続オブジェクトが必要なため、明示的に作成する必要があります。ここにあなたの使用量がどのように見えるかの例です:

(let [db-spec local-postgres 
     sql  "select * from big_table limit 500000 " 
     fetch-size 10000 ;; or whatever's appropriate 
     cnxn  (doto (j/get-connection db-spec) 
        (.setAutoCommit false)) 
     stmt  (j/prepare-statement cnxn sql :fetch-size fetch-size) 
     results (rest (j/query cnxn [stmt]))] 
    ;; ... 
) 

別のオプション

問題がqueryであると思われるので、with-query-resultsを試してみてください。これは廃止予定だと考えられていますが、まだそこにあり、動作しています。ここでは使用例です:

(let [db-spec local-postgres 
     sql  "select * from big_table limit 500000 " 
     fetch-size 100 ;; or whatever's appropriate 
     cnxn  (doto (j/get-connection db-spec) 
        (.setAutoCommit false)) 
     stmt  (j/prepare-statement cnxn sql :fetch-size fetch-size)] 
    (j/with-query-results results [stmt] ;; binds the results to `results` 
    (doseq [row results] 
     ;; 
    ))) 
+1

、作られた:結果タイプの順方向のみ、カーソルを追加しました読み込み専用にし、フェッチサイズを1000、そして100に設定しました。大きな結果セットをフェッチしようとすると、jvmヒープサイズが不足しています。私は新しいコードを含めるために上記の私の質問を更新しました...私はこの時点で何が熱望することができたのかを失っています... – joefromct

+0

@joefromct、自動コミットを無効にしてみてください - '(.setAutoCommit db-connection false) '。私は答えのサンプルコードに追加しました。一方、難しい点の1つは、 'setFetchSize'はドライバのヒントに過ぎないということです([API docs](http://docs.oracle.com/javase/1.5.0/docs/api/java/)。 sql/Statement.html#setFetchSize(int)))どのように解釈されるかはドライバによって異なる可能性があります。しかし、PostgreSQLの[JDBC docs](http://jdbc.postgresql.org/documentation/head/query.html)は、サポートされていることを示しているので、ちょうどよい命題を見つける必要があると思います。 – jbm

+0

明確にするために、 'setFetchSize'は、' prepare-statement'が ':fetch-size'引数に基づいて内部的に呼び出すメソッドです。 – jbm

1

私はより良い解決策を見つけました。カーソルを宣言して、トランザクション内のデータからチャンクを取り出す必要があります。例:

(db/with-tx 
    (db/execute! "declare cur cursor for select * from huge_table") 
    (loop [] 
     (when-let [rows (-> "fetch 10 from cur" db/query not-empty)] 
     (doseq [row rows] 
      (process-a-row row)) 
     (recur)))) 
ここ

db/with-txdb/execute!db/queryは自分のショートカットがdb名前空間で宣言されている:私は、接続を追加しました

(def ^:dynamic 
    *db* {:dbtype "postgresql" 
     :connection-uri <some db url>)}) 

(defn query [& args] 
    (apply jdbc/query *db* args)) 

(defn execute! [& args] 
    (apply jdbc/execute! *db* args)) 

(defmacro with-tx 
    "Runs a series of queries into transaction." 
    [& body] 
    `(jdbc/with-db-transaction [tx# *db*] 
    (binding [*db* tx#] 
     [email protected]))) 
+0

それに感謝、私はそれを撃つだろう。しかし、 'db/execute! 'では特定のpg sql構文を使用する必要がありますが、残念です。私はポストグルで練習していましたが、何かデータベースには無関係なものを構築しようとしていました。ありがとう、 – joefromct

関連する問題