- 投稿日:2020-11-21T21:30:30+09:00
簡単 (電子) レシート receiptline の新機能を試してみた
マークダウン言語で紙のレシートや電子レシートを簡単に作れる receiptline。
https://github.com/receiptline/receiptline
https://www.npmjs.com/package/receiptline少し前にマイナーバージョンアップがあったので、新機能を試してみました。
(2020年11月21日時点で Verion 1.1.1)Web フォントで表示
前回の記事で試していた Google Fonts の「Kosugi Maru」が採用されました。
これで iOS, Android, Windows, Linux, Mac どの環境でも同じ表示になるはずです。
データは過去の記事から。
ReceiptLine{image:iVBORw0KGgoAAAANSUhEUgAAASAAAAAwAQMAAACL7DXsAAAABlBMVEUAAAD///+l2Z/dAAABqElEQVQ4y+3VMW7bMBQGYLIqwqUo10ymj5AxBQwouUmO0NEFBFOCBo05Qs7QG5DV0K1noOGhKwMvLEDw7yOVALEUIB46BdUofJTe+x8pMZxxsXeFol4gJucm1HMUopght0SPmCO2RCOGGTJLNBB783UShzladidxfBMluYjpFaT+FYqEKE4O/8VtRA7WrRO8Sl+pEIPWq2Nd0LGPHvc81CJYYGgTfslwdy2wql1UP1RBv+2foFVHaO8AaYAHYW4uq1hLF1dsQj/tY9wpS2j0hAgq4TZVXkVP+jChwfrU1BPSSTkkJfxO2KAHQv1U04P1OhbUMh0z0oQk3e0zmrpD50UoqKdWCSGdopxT4oGfojBDNBZvIztF4ynKA95bsG1B3RMaFugw2qPfqmeUI5AzNODQU06NzMgkqpm6m9BTBGX7jn33PTYUy0qYBqki9PmiIApzW9Aeol1fxcR45MJxmjlDYm1GNBb/saDQCHO3/QZ2GyvuK6BdK/AuI6yU/yT6cjjVPqQK1kKqSIE42vPDwVxWTBvEzY69POb2nG/Bf4T2HMT0u/sj/AWoqMYMK2VNkgAAAABJRU5ErkJggg==}| ^^領収書| 2020/07/20 01:23| #NGC17TH| 缶ビール | ¥211~ シュークリーム | ¥129* - 小計 | ¥340~ (10%対象 | ¥211) ( 8%対象 | ¥129) (内消費税等 | ¥28) ^合計 | ^¥340 お 預 り | ¥1,000 お 釣 り | ¥660 *印は軽減税率対象商品です。USB (Bluetooth) プリンターで印刷
仮想シリアルポート対応 USB (Bluetooth) プリンターでテスト印刷できるようになりました。
そこで、ネットオークションで新たに SII RP-E10 と CITIZEN CT-S255 を入手。
どちらも USB 接続モデルで、店舗で予備機として保管されていたものだそうです。
仮想シリアルポートの設定
RP-E10 用「SII RP Series Communication Software for Windows」をインストール。
「RP通信設定ユーティリティ」で仮想シリアルポートドライバーに切り替え。
CT-S255 用「シチズンサーマルプリンタ仮想 COM ポートドライバー」をインストール。
デバイスマネージャーで確認。RP-E10 は「COM3」、CT-S255 は「COM7」になりました。
Serial-LAN Converter の設定
servers.json
で、COM3
(またはCOM7
) と TCP9100
ポートをつなぎます。servers.json"serial": { "host": "127.0.0.1", "port": 9100, "device": "COM3" }ReceiptLine Designer の設定
printers.json
に、RP-E10 と CT-S255 を登録。localhost
がプリンターになります。printers.json{ "rp_e10": { "host": "127.0.0.1", "port": 9100, "cpl": 48, "encoding": "cp932", "gamma": 1.8, "upsideDown": false, "spacing": false, "cutting": true, "command": "sii" }, "ct_s255": { "host": "127.0.0.1", "port": 9100, "cpl": 48, "encoding": "cp932", "gamma": 1.8, "upsideDown": true, "spacing": false, "cutting": true, "command": "escpos" } }テスト印刷
Node.js でサーバーを起動。ReceiptLine Designer で印刷できました。
この開発ツールの使い方はこちら。$ node designer.js
新しい印刷オプション
printers.json
で行間隔と用紙カットの有無が選択できるようになりました。行間隔
spacing
をtrue
にすると、行間が空いて読みやすくなります。
ただし、罫線もしくは縦倍角以上の拡大文字がある行の間隔は変わりません。
ちなみに、前々回の記事は、上下反転印刷に対応できない中途半端な改造でした…用紙カット
cutting
をfalse
にすると、印刷完了後に自動カットされなくなります。
また何か作ったら投稿します。ではまた!
- 投稿日:2020-11-21T18:58:01+09:00
【Jest】テスト用のAPIとDBとうまく付き合っていく方法
始めに
apiのnodeのバージョンを8から14にしました(まだリリースはしてない)!!
14への移行自体は簡単だったもののテストの移行でかなり苦戦しました。。。
テストは古のライブラリmocha-coで書かれており、仕方なくmocha-coのシンタックスをjestに置き換える作業を開始しました。これが予想以上にめんどくさくその時のHow toを解決する方法がこの記事になります。
以下の問題にお悩みの方はこちらを参考していただけるかと思います。またいい方法をご存知の方はお教えください!!
- TooManyConnectionとなってしまう
- 毎回のテストでサーバー立ち上げとクローズをするのめんどくさい
- --ranInBandつけたけどなんか挙動がよくわからない・・・。
- mochaからのjestに移行したい
正確にjestのランタイムの仕様が分かっているわけではなく、調べたり試したりした結果なので間違いやもっと詳細が分かる方がいらっしゃいましたらぜひお教えください!!
また正確なコードを書くと記述量が多いので雰囲気で書いているところもあるのでご了承ください。前提条件
- テスト用のDBを立ててデータを流しこみながら行っている
- モジュール単位のテストではなくApiに対してリクエストを投げてそのレスポンスとDBのデータを確認してテストを行う
といったことを前提とします。データベースはインメモリではなくデータベースサーバーを立てる前提です。ですのでテストは順次実行をしていく必要があります。(もともとそうなっていたのが理由としては大きいですが)実際の環境に近い環境を立てることができることがこの作りのメリットだったりします。逆にインメモリにすると並列でテストを実行することができるので、実行速度をあげることができます。
では、データベースサーバーを立てるテストの場合、順次実行をしていかないといけない理由としては、
こんな感じに一斉に更新がかかると他のテストに影響が出ることがあります。
たとえば、テスト前にテーブルを消したり、データの更新をしたりとか・・・。すると想定されていた状態にないデータが生まれテストが落ちたします。しかも恐ろしいことにそれが処理時間によって変わるのでテストガチャが生まれます。
そのためこの場合は、順次実行をしていくのがよいです。こうすればテストファイルが一斉に更新されることによっておこるデータベースの不整合を防ぐことができます。
そのためのオプションとして、jestでは--runInBand
オプションを利用します。jest --runInBandただし、これだけでは問題が発生します。
テスト用のAPIサーバーが立ち上がらない or 立てたり切ったりし続けると遅い問題
具体的にどういう問題かについての前にAPIテストで行うことを書きます。
APIテスト
APIのテストでは
- モジュール単位でテストする方法
- APIサーバーを立ててリクエストを送ることによって
があります。後者の方法で有名なライブラリとしては
supertest
が挙げられます。
例えばexpress
とsupertest
の両方を使ってサーバーを立てるコードは下記のように書けます。// app.js const express = require('express'); const app = express(); app.get('/', function (req, res) { res.send('Hello World'); }); module.exports = app;// test-request.js const supertest = require('supertest'); const app = require('./app'); module.exports = supertest.agent(app.listen(3000));// index.test.js const testRequest = ('./test-request'); describe('test', () => { it('200?', () => { testRequest .get('/') .expect(200); }); });またテスト用のデータを流し込むためのモジュールも下記のように定義します。
// test-model.js const Sequelize = require('sequelize'); const sequelize = new Sequelize('test', 'test', 'test', { host: '127.0.0.1', dialect: 'mysql', }); const models = {...}; module.exports = { sequelize: sequelize, models: models };mochaの場合はtest-requestやtest-modelを読み込んでテストをすればそれでよく、問題は発生しません。
しかし、jestの場合はそうはいきません。mochaにおける挙動
といった形になります。そのため先ほどのコードで一度しかコネクションはできないですし、一度しかサーバーは立ち上がりません。
jestにおける挙動と問題
jestにおいてはどのようになるのかというと、
といった形になります。そのためmochaのときのような書き方はやめてテストファイルごとに『立てて、切って』とする必要があります(書き換えの際はめっちゃ大変です)。
とはいえ、毎回そのようにするのは結構大変ですし、毎回『起動、コネクト、停止、切断』と実行していくとテスト完了にかかる時間が増えてしまいます。
- 毎回のテストでサーバーの起動・停止、dbへのコネクション・停止をする必要がある
- 記述量が増え、closeを忘れるとテストが落ちる
- 処理が増えるため必然的に実行時間が長くなる
- エラーハンドリングをしてcloseしなくても動くようにする
- 大量のコネクションが貼られることになりtoo many connectionとなってしまう
といった問題が発生します。
そのためには、globalSetupとtestEnvironmentを使います。
globalSetupを使い一度のみ起動する
globalSetupを使います。
jestではこういった形で各ファイルが実行されるため、一度しか実行されないglobalSetupでサーバーの起動をなどを行うのが良いかと思われます。
// globalSetup const supertest = require('supertest'); const app = require('./app'); global.__request = supertest.agent(app.listen(3000));// test-utils/request.js module.exports = global.__request;// test.ts import request from 'test-utils/request'; describe('test', () => { it('200', () => { request.get('/').expect(200); }); });気持ち的にこんな感じにglobalにロードしたモジュールを保存させてテストで利用できるようにしたいんですが、ここにはここで落とし穴があります。その落とし穴はsetUpFilesにあります。
setUpFilesでは何を行っているのかというか『あたらしい環境のセットアップです』具体的にはglobalをはじめとしたテスト実行用の環境を生成するタイミングになり、新しくglobalを作っているため、下記のようなコードの場合、// globalSetUp global.__global = 'I am from global setup';// setUpFiles console.log(global.__global); // <- 新しいコンフィグになるためundefined global.__setUp = 'I am from setup files'; // <- ただしここで追加したglobalメンバーにはテストファイルがアクセス可能となります。そのため、実際のテストファイルでも
// test.js describe('test', () => { console.log(global.__global); // undefined console.log(global.__setUp); // I am from setup files;となります。
親玉のランナーの実行環境とは別の環境が作られるというところがポイントになります。
setUpAfterEnvもタイミングは異なれど同じ挙動をします。そこでどうするのかというとtestEnvironmentを利用します。
testEnvironment
testEnvironmentを使うことで柔軟にテスト環境を構築することができます。
// package.json "testEnvironment": "./my-custom-environment.js",// my-custom-environment const NodeEnvironment = require('jest-environment-node'); class CustomEnvironment extends NodeEnvironment { constructor(config, context) { super(config, context); } async setup() { this.global.__request = global.__request; await super.setup(); } } module.exports = CustomEnvironment;このようにsetupメソッドで
global.__request
をthis.global__request
に設定することによってテストファイルの方でglobal.__request
にアクセスすることができます。まとめ
最終的にはこのようになります。
アプリケーションコード
// app.js const express = require('express'); const app = express(); app.get('/', function (req, res) { res.send('Hello World'); }); module.exports = app;// model.js const Sequelize = require('sequelize'); const sequelize = new Sequelize('test', 'test', 'test', { host: '127.0.0.1', dialect: 'mysql', }); const models = {...}; module.exports = { sequelize: sequelize, models: models };テストユーティリティコード
// test-request.js module.exports = global.__request;// test-model module.exports = global.__db;// global-setup.js const supertest = require('supertest'); const app = require('./app'); const db = require('model'); global.__request = supertest.agent(app.listen(3000)); global.__db = db;// my-custom-environment const NodeEnvironment = require('jest-environment-node'); class CustomEnvironment extends NodeEnvironment { constructor(config, context) { super(config, context); } async setup() { this.global.__request = global.__request; this.global.__db = global.__db; await super.setup(); } } module.exports = CustomEnvironment;// jest.config.json { "testEnvironment": "./global-setup.js", "globalSetup": "./my-custom-environment.js" }テストコード
// test.js const testRequest = ('./test-request'); const testModel = ('./test-model'); describe('test', () => { beforeAll(async () => { await testModel.reset(); }); it('200?', () => { testRequest .get('/') .expect(200); }); });全体図
こんな感じに実装することができます。
また、mockを使ってinjectionすることも可能です。Mockを使ったインジェクションによる方法
この方法では、管理するファイルは増えるもののアプリケーション内でデータベースなどにコネクトするためのモジュールなどを一括で変更できる点が優秀です。
テストユーティリティコード
// test-request.js const supertest = require('supertest'); const app = require('./app'); module.exports = supertest.agent(app.listen(3000));// test-model const db = require('model'); module.exports = db;// global-setup.js global.__request = require('./test-request'); global.__db = require('./test-model');// setup-after-env.js jest.mock('test-request', () => global.__request); jest.mock('test-model', () => global.__db);// my-custom-environment const NodeEnvironment = require('jest-environment-node'); class CustomEnvironment extends NodeEnvironment { constructor(config, context) { super(config, context); } async setup() { this.global.__request = global.__request; this.global.__db = global.__db; await super.setup(); } } module.exports = CustomEnvironment;// jest.config.json { "testEnvironment": "./global-setup.js", "globalSetup": "./my-custom-environment.js", "setupFilesAfterEnv": "./setup-after-env.js", }テストコード
// test.js const testRequest = ('./test-request'); const testModel = ('./test-model'); describe('test', () => { beforeAll(async () => { await testModel.reset(); }); it('200?', () => { testRequest .get('/') .expect(200); }); });問題点
ただし、jestのmockと相性が悪いという問題点があります・・・。
というのもglobal上にサーバーコードがロードされているため、それらを実行したい時は各ファイルごとに一回サーバーを落としてもう一度ロードする必要があります。
そのため、globalにこれらをやるための入り口を開けてあげる必要がありました。
ここら辺みなさんどうしてるんですかね?いろいろとご意見伺いたいのもあって書かせていただきました。
- 投稿日:2020-11-21T16:26:39+09:00
【Electron入門】GithubのIssueを一瞬で確認できるアプリを作って業務効率化してみた #2 GithubAPI編
はじめに
前回はwindowの作成をしました。
今回はGithubAPIを導入して完成させようと思います。
長くなってしまうのでJavaScriptでDOMの作成をする部分やCSSの説明は省きます。
気になる方はコードをご確認ください!完成品
option(alt) + Spaceで表示/非表示が切り替えられます。
現状
Access Tokenを生成する
それではやっていきましょう。
Githubにログインし、
Settings > Developer settings > Personal access tokens
と進みます。
Generate new token
を押すとtoken作成画面に進めます。・
Note
にはtokenを識別するために用途を書きます。自分はgit-electron
としました。・
Select scopes
でtokenの権限を選択できます。今回はrepo
の権限全てにチェックをつけます。
Generate token
を押すとAccess Tokenが生成されるので、
後ほど使うので、赤く囲まれている部分のAccess Tokenをコピーしておきます。AccessTokenを使ってAPIにアクセスする
実際にGithubAPIを叩いてみます。
通信には
axios
を使用しました。
Tokenの管理にはdotenv
を使用しました。1 . アプリのルートパスで
.env
ファイルを作成して以下のように編集します。.envGH_ACCESS_TOKEN=[先ほどコピーしたAccess Token] GH_BASE_URL=https://api.github.com2 . main.jsに以下を追記して
.env
ファイルを読み込みます。main.jsrequire("dotenv").config({ path: __dirname + "/.env" });※ AccessTokenは外部に見られたくないので、Githubで公開する際には
.gitignore
を作成してremoteに送らないようにしましょう。.gitignore.env
3 . axiosを拡張するaxiosの設定を共通化するために拡張します。
自分はplugins
ディレクトリを作成して、配下にaxios.js
を作成しました。plugins/axios.jsconst axiosBase = require("axios"); const axios = axiosBase.create({ baseURL: process.env.GH_BASE_URL, headers: { Authorization: "Bearer " + process.env.GH_ACCESS_TOKEN, "Content-Type": "application/json", }, }); module.exports = axios;これ以降、axiosを呼び出すときはこのaxios.jsを呼び出します。
これでGithub APIを呼び出す準備は整いました!
フロント(Window)からAPIを呼び出す
Electronには
メインプロセス
とレンダラープロセス
があります。メインプロセス内(ここで言うmain.js)でBroeserWindowインスタンスを作成し、そのBroeserWindowインスタンスがレンダラープロセス内でWebページ(html)を表示します。
公式ドキュメントよってhtmlから呼び出したJavaScriptなどはレンダラープロセスで実行する事になります。
ここで困ることが一点あります。
main.js
はメインプロセス上で動くので、Node.jsの文法が使えますが、レンダラープロセスでは使えません。
require
などNode.js上で使えるメソッドをフロントからの要求に応じて使えるようにするには、
contextBridgeを使って、メイン⇔プロセス間の通信をセキュアにする必要があります。
詳しく知りたい方は以下の記事がうまくまとまっていると思うので参考にしてみてください!ElectronでcontextBridgeによる安全なIPC通信 - Qiita
ElectronでcontextBridgeによる安全なIPC通信(受信編) -QiitacontextBridgeを実装する
contextBridgeを使用する為の
preload.js
をルートディレクトリに作成します。preload.jsconst { contextBridge } = require("electron"); contextBridge.exposeInMainWorld("api", { github: { getIssues: () => { // メインプロセスで行いたい処理を書く const axios = require("./plugins/axios"); axios .get("/issues") .then((res) => { console.log(res); }) .catch((e) => console.log(e)); }, }, });次にmain.jsを修正して、BrowserWindowでpreload.jsを読み込むよう設定します。
main.jsconst win = new BrowserWindow({ webPreferences: { nodeIntegration: false, contextIsolation: true, preload: __dirname + "/preload.js", }, // .....省略...... })最後にindex.htmlにscriptタグと発火用のボタンを追加して、preload.jsで定義した関数を呼び出せるようにします。
index.html<body style="-webkit-app-region: drag"> <div style="background: #fff; height: 300px"> <h1>Hello World!</h1> <button onclick="fetchAPI()">get issues</button> </div> </body> <script> function fetchAPI() { window.api.github.getIssues(); } </script>ボタンを押して、Electron上でDeveloper toolを開くとIssueのデータが取れていると思います!
デスクトップアプリとして使いたい場合は、
npm run build
でelectron-builderが起動し、dist
配下にアプリが生成されます。DOMの生成やCSSはリポジトリを参考にしてみてください!
最後に
今回はElectroとGithubAPIを組み合わせて業務効率化アプリを作ってみました。
かなり手軽にデスクトップアプリを作成することができた一方、いくつか制限もあったので大規模開発での導入には検討が必要かもしれません。(と言ってもVSCodeなどもelectronで開発されているのである程度安心していいと思っています)
こんな感じで週1~2でアウトプットを記事にしていこうと思います。是非LGTM、フォローの方お願い致します!
Twitterのフォローもお願い致します!
https://twitter.com/1keiuu
- 投稿日:2020-11-21T16:25:31+09:00
【Electron入門】GithubのIssueを一瞬で確認できるアプリを作って業務効率化してみた #1 Alfredっぽいwindow作成編
完成品
option(alt) + Spaceで表示/非表示が切り替えられます。
ソースコード
https://github.com/ikkei12/git-app.pub
はじめに
Alfred最高ですよね。
彼のおかげでマウスを使う頻度が激減して助かっています。Alfredと同じ手軽さで、自分がアサインされているissueを確認できるデスクトップアプリが作りたいなと思ったので、Electronで作ってみようと思いました。
Electronには
- JavaScriptで作れる
- Chromiumベースなのでwebエンジニアにも優しい
といった利点があり、HTML/CSS/JavaScriptを学んだ初心者の方でも扱いやすいかと思います!
Electronのはじめかた
基本的に公式ドキュメントを参考にElectronのセットアップをするだけですが、一部変更を加えています。
1. 以下のコマンドを実行
mkdir git-app && cd git-app npm init -y npm i --save-dev electron npm i --save-dev electron-builder2. html/jsファイルを作成
標準的なNode.jsアプリと同様に以下のような構成で始めます。
git-app/ ├── package.json ├── main.js └── index.htmlmain.jsconst { app, BrowserWindow } = require('electron') function createWindow () { const win = new BrowserWindow({ webPreferences: { nodeIntegration: false, contextIsolation: true, }, frame: false, alwaysOnTop: true, useContentSize: true, transparent: true, }) win.loadFile('index.html') win.on("blur", (e) => { // window外をクリックしたタイミング(blur)で閉じる app.hide(); }); } app.whenReady().then(createWindow) app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit() } }) app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) { createWindow() } })index.html<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>Hello World!</title> <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" /> </head> <body style="-webkit-app-region: drag"> <div style="background: #fff; height: 300px"> <h1>Hello World!</h1> </div> </body> </html>3. package.jsonを修正
package.jsonは以下のように書き換えます。
package.json{ "name": "git-app", "version": "1.0.0", "description": "", "main": "main.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "NODE_ENV=development electron .", "build": "NODE_ENV=production electron-builder" }, "build": { "appId": "git-app", "mac": { "category": "git-app" } }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "electron": "^10.1.5" } }4. 起動
npm start
でこのようなwindowが表示されると思います。どこからでもショートカットでアプリを呼び出せるようにする
このままではwindowの表示/非表示を切り替えられないし、PCの起動時に毎回アプリを立ち上げる必要があります。
Alfredのような挙動を実現するには
1. electronアプリを常駐で起動させておいて、
2. 表示/非表示を切り替えるショートカットを登録する
必要があります。1. electronアプリを常駐で起動させる
main.jsapp.dock.hide(); if (process.env.NODE_ENV !== "development") { app.setLoginItemSettings({ openAtLogin: true, path: app.getPath("exe"), }); }解説
・
app.dock.hide()
常駐のアプリをDockに表示したくないので、Dockから非表示にします。・
app.setLoginItemSettings()
でログイン時に自動で起動してくれるようになります。
ローカルでは起動したくないので、NODE_ENV
で判定しています。・macではログイン時に起動されるアプリは
ユーザーとグループ > ログイン項目
に追加されます。
2. 表示/非表示を切り替えるショートカットを登録する
main.jsapp.on("ready", function () { globalShortcut.register("alt+space", function () { // 現在focusしているwindowを取得 const window = BrowserWindow.getFocusedWindow(); // windowが存在すればhide、なければshow window ? hideWindow(window) : showWindow(); }); }); app.on("will-quit", function () { // 終了するタイミングで全てのglobalShortcutを解除 globalShortcut.unregisterAll(); }); function showWindow() { // focusさせる事でBrowserWindowのblurイベントを検知させる app.focus({ steal: true }); app.show(); } function hideWindow(window) { // center()する事でshowする時に中央で表示される window.center(); app.hide(); }解説
・アプリが
ready
状態になった段階で、ショートカットキーを登録しています。自分はalt+space
で起動したいのでregister
の第一引数に渡しています。・第二引数ではショートカットキーが押されたタイミングで実行する関数を登録しています。現在focusされているwindowを取得し、存在する場合はhideメソッド、しなければshowメソッドを実行する事で表示を切り替えています。
・
globalShortcut
と言うのがポイントで、普通のショートカット(localShortcut)ではElectronアプリがフォアグラウンドになっている場合にしか反応しません。
このように動くと思います!
Tips
Windowをドラッグできるようにする
<body style="-webkit-app-region: drag">参考: https://www.electronjs.org/docs/api/frameless-window#draggable-region
まとめ
ご覧いただきありがとうございました!
次回はGithub APIからIssueを取得して表示する部分を作成していきます!Twitterのフォローもお願い致します!
https://twitter.com/1keiuu
- 投稿日:2020-11-21T11:23:35+09:00
Elasticsearchのindexの引越し。Elasticdumpはlimitオプション再定義で高速処理。
はじめに
Elasticsearch を docker を使って、ローカルマシンで使用している。
Elasticsearchのバージョンをあげてみたくなったので、データをDumpして引越ししてみることにした。事前調査
2020/11/20現在、Googleを使って、「elasticsearch インデックス 移行」と調べてみると、Elasticdumpというスクリプトを使っているひとが多いようだ。
(以下、Googleの検索結果 表示される順に抜粋)
ElasticSearchのindexデータを移行する - Qiitaqiita.com › Node.js
https://qiita.com/taai/items/9244e7dc4dacf29ab95aよく使うElasticSearchのクエリ(elasticdump) - Qiitaqiita.com › Elasticsearch
https://qiita.com/nakazii-co-jp/items/3199433d685d0600c6d6Elasticsearchのデータをdumpしてコピーしたい - DRYな備忘録otiai10.hatenablog.com › entry › 2015/03/04
https://otiai10.hatenablog.com/entry/2015/03/04/152842Elasticsearch 上のデータを割と簡単にダンプして他の ...inokara.hateblo.jp › entry › 2020/05/02
https://inokara.hateblo.jp/entry/2020/05/02/074402...
..
.elasticsearch-dump/elasticsearch-dump: Import and ... - GitHubgithub.com › elasticsearch-dump › el...
https://github.com/elasticsearch-dump/elasticsearch-dumpQiita先輩方の記事の作成日が5年前であっったりしたが、githubは2020年に入っても頻繁にメンテナンスされているようだ。elasticsearchのバージョンは気にせず使えるのではないだろうか。
Google検索結果で表示される情報は、やや作成日が古いものもあることが気になったが、2020年もメンテされているなら使っても問題は少ないのではないか、と期待して、私もこの node.js スクリプトを使ってみることとした。準備
elasticdump は、nodeスクリプトなので、マシンにnode実行環境が必要であった。私の環境ではすでにnode実行環境があるので npm install elasticdump した(あとで気づいたのだがdockerもあるようだ。)。
実行1 ( dump output=json, input=elasticsearch/index )
事前調査のいずれの先輩たちも、「elasricdump --output ^^^ --input ~~~」と、インプットとアウトプットの指定だけでdumpをし、dumpをelasticsearchに読み込ませているので、そのまま真似してやってみたのだが・・・
ドキュメント数が1200万を超え、ストレージサイズが6GBを超えていた私のindexではjsonのdumpデータの出力完了までに先輩たちと同じやり方では、24時間かかった。
出力されたファイルのサイズ: 5302184689 byte<参考:マシン環境> intel i7-3770k, 16GB Memory, SSD 960GB
<参考:docker Engine> v19.03.13
<参考:docker resources> CPUs:4, Memory:5.50GB, Swap:2GB, Disk image size:59.6GB振り返り1
そんなに時間かかるものなのか?
動作ログを見てみる・・・なるほど、objectを100ずつ扱っていたのか。
これ、もっと増やせないのか?elasticsearch-dump のGitHubを読むと、設定オプションがあった。
--limit How many objects to move in batch per operation limit is approximate for file streams (default: 100)よし、では、dumpしたJsonを新環境に書き込むにあたっては、「--limit」オプションを追加しよう。
実行2 ( dump output=elasticsearch/index, input=json )
実行1の output と input のオプションの対象を入れ替え、 さらに limitオプションを付け足して実行する。いちどにあつかうオブジェクトの量を増やした効果を見てみた。limitの値をどの程度にするのが適切なのかわからなかったが、今回は なんとなく8000 とした。
この設定では、処理時間は 1時間半弱 であった。 概算でも16倍ほど高速だ。
振り返り2
limitの値を80倍に設定して、処理時間は24時間から1時間半へと16分の1になった。この程度の時間であれば、まだ個人利用のPCであっても我慢できる範囲だろう。まだどこかほかにボトルネックがあるのだろうが、このスクリプトで処理時間を変動させる要因のひとつはlimit値であることはまちがいない。次の機会があれば、さらにlimit値をあげてみたい。ただし、適切な値の決定方法がわからない。技術者の勘と経験で決めていいのだろうか。
まとめ
Elasticsearchのデータ移行にあたり、elasticdumpスクリプトを使った。inputとoutputのデータダンプの方向の違いはあるが、limitオプションの設定値をデフォルトの数十倍にして処理速度を比較した。その結果、limitオプションの設定値を大きくすることで、処理時間を大幅に短縮できることがわかった。