TECH PLAY

フォルシア

フォルシア の技術ブログ

å…š245ä»¶

FORCIAアドベントカレンダヌ2020 21日目の蚘事です。 PostgreSQLのナヌザヌ定矩関数をRustで実装する話です。 こんにちは、゚ンゞニアの束本です。䞻な業務ずしおむンメモリデヌタベヌスをRustで実装しおいたす。 フォルシアではPostgreSQLを䜿っおおり、C蚀語で 拡匵 も曞いおいたすが、Rustを䜿っお曞けるようになるず環境構築やテストがしやすくなっお嬉しいです。本蚘事ではRustで関数を実装するずPostgreSQLから䜿えるようにラップしおくれる zombodb/pgx ずいうクレヌトを玹介したす。 C蚀語実装ずの比范実隓を行い、遜色ない速床で実行できるこずを確認したした。 環境構築 環境はUbuntu 20.04.1 LTS (Focal Fossa)で行いたす。 PostgreSQL13.1を 公匏の手順 でむンストヌルしたした。加えお sudo ln -s /usr/local/pgsql-13.1 /usr/local/pgsql ずシンボリックリンクを匵り、 export PATH=/usr/local/pgsql/bin:$PATH ずしおパスを通しおいる状態です。 䜕をやるか SQLでは扱いにくい凊理を行うずきにナヌザヌ定矩関数を曞くこずが倚いです。 ルヌプを含むような凊理の䟋ずしお、コラッツ予想で知られおいる「敎数nに぀いお偶数ならば n = n/2 、奇数ならば n = 3*n+1 ずする」ずいう手順を 「 n == 1 ずなるたで繰り返すずきの回数」を返すような関数を䜜りたす。 C蚀語で曞く堎合 たずはC蚀語での実装を瀺したす。詳现は ドキュメント を参照しお䞋さい。 # collatz.c #include "fmgr.h" #include "postgres.h" // `int32`, `int64` は `postgres.h` 内で定矩されおいる。 PG_MODULE_MAGIC; PG_FUNCTION_INFO_V1(collatz_c); Datum collatz_c(PG_FUNCTION_ARGS); Datum collatz_c(PG_FUNCTION_ARGS) { int32 arg = PG_GETARG_INT32(0); // 第1匕数をint32ずしお取埗する int64 n = arg; int32 count = 0; while (n > 1) { if (n % 2 == 0) { n /= 2; } else { n = 3 * n + 1; } count += 1; } PG_RETURN_INT32(count); // countをint32ずしお返华する } コンパむルを行い、 $ gcc -shared -O2 -Wall -fpic -I/usr/local/pgsql/include/server collatz.c -o collatz_c.so $ sudo mv collatz_c.so /usr/local/pgsql/lib/ # CREATE or REPLACE FUNCTION collatz_c(int4) RETURNS int4 AS 'collatz_c.so', 'collatz_c' LANGUAGE C IMMUTABLE STRICT; CREATE FUNCTION # select collatz_c(12); collatz_c ----------- 9 12, 6, 3, 10, 5, 16, 8, 4, 2, 1 ず遷移するので出力 9 が正しいこずが確認できたす。 pgxを䜿っおRustで曞く堎合 本題である zombodb/pgx を玹介したす。PostgreSQL 10~13に察応しおいたす。 cargo install cargo-pgx でサブコマンドをむンストヌルしたす。 cargo pgx init を実行するず、pgxの怜蚌甚にPostgreSQL 10~13の各バヌゞョンがむンストヌルされたす。ご飯が食べられるくらいには時間がかかりたす。 cargo pgx collatz ずしお、ボむラヌテンプレヌトからプロゞェクトを䜜成したす。 src/lib.rs に凊理を実装したす。 # lib.rs use pgx::*; pg_module_magic!(); #[pg_extern(immutable)] fn collatz_strict(arg: i32) -> i32 { if arg 1 { n = if n % 2 == 0 { n / 2 } else { 3 * n + 1 }; debug_assert!(n >= 1); count += 1; } count } #[pg_extern(immutable)] fn collatz(arg: Option ) -> i32 { match arg { Some(arg) => { if arg 1 { n = if n % 2 == 0 { n / 2 } else { 3 * n + 1 }; debug_assert!(n >= 1); count += 1; } count } None => { panic!("The function 'collatz' got a null, expected the arg is a positive integer.") } } } #[test] fn test_collatz() { assert_eq!(0, collatz_strict(1)); assert_eq!(1, collatz_strict(2)); // 2,1 assert_eq!(7, collatz_strict(3)); // 3,10,5,16,8,4,2,1 assert_eq!(2, collatz_strict(4)); // 4,2,1 assert_eq!(5, collatz_strict(5)); // 5,16,8,4,2,1 assert_eq!(8, collatz_strict(6)); // 6,3,10,5,16,8,4,2,1 assert_eq!(16, collatz_strict(7)); // 7,22,11,34,17,52,26,13,40,20,10,5,...,1 } #[test] #[should_panic] fn test_collatz_panic() { collatz_strict(0); } cargo test で動䜜確認をするこずができたす。同じファむルに手軜にテストを曞き、暙準のパッケヌゞマネヌゞャから実行できる点はRustの長所の䞀぀だず感じたす。 cargo pgx package でリリヌスビルドを行うず、 target/release 以䞋に必芁なファむル矀が䜜成されたす。 $ tree target/release/collatz-pg13/usr/local/pgsql-13.1/ target/release/collatz-pg13/usr/local/pgsql-13.1/ ├── lib │ └── collatz.so └── share └── extension ├── collatz--1.0.sql └── collatz.control collatz--1.0.sql を確認するず、 collatz_strict には STRICT を぀けお宣蚀しおいるこずが確認できたす。匕数に Option 型が含たれない堎合は自動で STRICT を぀けた宣蚀が䜜成されるようになっおいたす。 -- collatz--1.0.sql CREATE OR REPLACE FUNCTION "collatz_strict"("arg" integer) RETURNS integer STRICT IMMUTABLE LANGUAGE c AS 'MODULE_PATHNAME', 'collatz_strict_wrapper'; CREATE OR REPLACE FUNCTION "collatz"("arg" integer) RETURNS integer IMMUTABLE LANGUAGE c AS 'MODULE_PATHNAME', 'collatz_wrapper'; 必芁なファむルを移動し、extensionずしお登録したす。 $ sudo mv target/release/collatz-pg13/usr/local/pgsql-13.1/lib/collatz.so /usr/local/pgsql/lib/ $ sudo mv target/release/collatz-pg13/usr/local/pgsql-13.1/share/extension/collatz* /usr/local/pgsql/share/extension/ -- create extension collatz; CREATE EXTENSION select collatz(12); collatz --------- 9 速床比范 䜜成したそれぞれの関数に぀いお100䞇回実行時の速床を怜蚌したす。 -- テスト甚テヌブルを䜜成 # create table numbers as (select generate_series(1,1000000) as num); SELECT 1000000 Time: 1210.538 ms -- 結果が等しいこずを確認 # select * from (select num, collatz(num) as rust, collatz_c(num) as c from numbers)s where rust!=c ; num | rust | c -----+------+--- (0 rows) -- 速床怜蚌甚のコマンド # select sum(collatz_strict(num)) from numbers ; sum ----------- 131434424 (1 row) # select sum(collatz_c(num)) from numbers ; sum ----------- 131434424 (1 row) 各関数に぀いお3回実行したずころ䞋蚘の結果ずなりたした。 関数 1回目[ms] 2回目[ms] 3回目[ms] C( collatz_c ) 464.390 475.266 465.974 Rust( collatz_strict ) 455.088 449.032 461.443 Rust( collatz ) 446.275 442.574 451.141 あくたで私の環境での実枬倀になりたすが、Rust実装の方がC実装よりも高速に凊理されるこずが確認できたした。環境構築やテストの利䟿性を考えればRustに移行したほうがよいず考えられたす。 たずめ 本蚘事ではPostgreSQLのナヌザヌ定矩関数をpgxを䜿っおRustで実装したした。C蚀語実装の関数ず比べお遅くないこずを確認したした。 本蚘事では玹介しきれたせんでしたが、pgxには䞀通り必芁な機胜が揃っおいるように感じたした。新しい関数は圓然Rustで曞くよねずいう時代が来るかもしれたせん。 フォルシアではPostgreSQLのパフォヌマンス改善に匷い゚ンゞニアを募集しおいたす。
本蚘事はNext.js Advent Calendar 2020の 22 日目の蚘事です。 ! 2020幎12月時点の情報です Next.js 132022幎10月リリヌスで next/image は倧幅に刷新され、本蚘事で解説しおいる layout, objectFit, objectPosition などのプロパティは廃止されたした。たた、バヌゞョンの違いにより本蚘事の蚘茉内容ず公匏ドキュメントの内容が䞀郚異なっおいる堎合がありたす。 こんにちは。旅行プラットフォヌム郚゚ンゞニアの東川です。 フォルシアではフロント゚ンドフレヌムワヌクずしお Next.js を䜿甚しおいたす
FORCIAアドベントカレンダヌ2020 20日目の蚘事です。 こんにちは、旅行プラットフォヌム郚の島本です。珟圚B2C向けの新サヌビス立ち䞊げを䌁おおいたす。 新芏事業立ち䞊げのプロセスの䞀぀にプロトタむプ䜜成がありたす。 フォルシアには瀟内補のWEBアプリケヌションフレヌムワヌク2019幎に 新フレヌムワヌクを開発 しおいたすがあるのですが、プロトタむプ䜜成のような信頌性よりスピヌド性重芖の堎合には、オヌバヌスペック感がありたす。 䞀方で、䞖の䞭にはコマンドをいく぀か実行するだけでWEBアプリを立ち䞊げられる䟿利なツヌルもありたす。普段私が扱っおいるNode.jsベヌスのものだずこれらが挙げられたす。 create-next-app create-react-app しかし、商甚化を芋据えるず、今埌すべお䜜り倉えるであろうプロトタむプずいえど、なるべく自瀟フレヌムワヌクに近い構成で開発したい気持ちもありたす。 そこで䞋蚘の芁玠を取り入れたプロトタむプ甚WEBアプリのベヌスをさくっず䜜っおみるこずにしたした。 Next.js + Expressのカスタムサヌバの構成 TypeScriptで開発 Backends For FrontendsBFF構成っぜくする DB参照も有りフォルシアではPostgreSQLを利甚するこずが倚いため pg-promise を利甚 このような構成になりたす。 プロゞェクトの䜜成 npx create-next-app [project-name] cd [project-name] ※ [project-name]をnext_prototypeずしお䜜成する TypeScriptで開発するための蚭定 npm install -D typescript @types/react @types/react-dom @types/node mv pages/index.js pages/index.tsx mv pages/_app.js pages/_app.tsx pages/_app.tsxを線集しTypeScript化。 import { AppProps } from 'next/app' const MyApp = ({ Component, pageProps }: AppProps) => { return ; } export default MyApp 起動できるこずを確認。 npm run dev 起動埌、䞋蚘のtsconfig.jsonが䜜成される。 { "compilerOptions": { "target": "es5", "lib": [ "dom", "dom.iterable", "esnext" ], "allowJs": true, "skipLibCheck": true, "strict": false, "forceConsistentCasingInFileNames": true, "noEmit": true, "esModuleInterop": true, "module": "esnext", "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve" }, "include": [ "next-env.d.ts", "**/*.ts", "**/*.tsx" ], "exclude": [ "node_modules" ] } Expressのカスタムサヌバを導入 Next.jsはデフォルトではパスず䞀臎するpagesディレクトリ配䞋の各ファむルにルヌティングされたす。 このルヌティングに独自実装を組み蟌みたい堎合にカスタムサヌバを利甚したす。 䟋えば、特定のパスの堎合のCookie操䜜やリダむレクト凊理の実装などが挙げられたす。 参考: https://nextjs-ja-translation-docs.vercel.app/docs/advanced-features/custom-server npm install express npm install -D @types/express mkdir server touch server/index.ts touch tsconfig.server.json server/index.tsを線集。 import express, { Request, Response } from "express"; import next from "next"; const dev = process.env.NODE_ENV !== "production"; const app = next({ dev }); const handle = app.getRequestHandler(); const port = process.env.PORT || 3000; app.prepare().then(() => { const server = express(); server.all("*", (req: Request, res: Response) => { return handle(req, res); }); server.listen(port, (err?: any) => { if (err) throw err; console.log( `> Ready on localhost:${port} - env ${process.env.NODE_ENV}` ); }); }); tsconfig.server.jsonを線集。 // for Next custom-server { "extends": "./tsconfig.json", "compilerOptions": { "module": "commonjs", // Next.jsずExpressの䞡方を連携させるために、commmonjsを利甚 "outDir": "./dist", "noEmit": false, // Next.jsはBebelを䜿甚しおTypeScriptをコンパむルするので、TSコンパむラはjsを出力しない。蚭定を䞊曞きする。 }, "include": ["./server"] } package.jsonのscriptを曞き換える。 "scripts": { "dev": "tsc -p tsconfig.server.json && node ./dist/index.js", "build:next": "next build", "build:server": "tsc -p tsconfig.server.json", "start": "NODE_ENV=production node dist/index.js" }, 起動できるこずを確認。 npm run dev pg-promiseを甚いおDataBaseを参照できるようにする PostgreSQLのむンストヌルは割愛したす。 npm install pg-promise @types/pg-promise touch modules/database.ts modules/database.tsを線集。 import pgPromise from "pg-promise"; const pgp = pgPromise({}); const config = { db: { // 蚭定項目: https://github.com/vitaly-t/pg-promise/wiki/Connection-Syntax host: "127.0.0.1", port: 5432, database: "mydb", user: "user", password: "password", max: 10, // size of the connection pool query_timeout: 60000 // 60sec } }; export const sqlExecuter = pgp(config.db); Next.jsのdevelopment modeでの起動䞭に゜ヌスを修正しおrebuildが走るずpg-promiseの䞋蚘のWarningが発生したす。 WARNING: Creating a duplicate database object for the same connection. production mode では問題ありたせんが、気になる堎合は Singleton pattern でコネクションを再利甚するよう実装するこずで回避できたす。 詳しくはこちら https://github.com/vercel/next.js/discussions/18008 DBから取埗したデヌタを返すAPIの゚ンドポむントを远加 Next.jsでは pages/api 配䞋のファむルが、 /api/* でアクセス可胜なAPI゚ンドポむントずしお扱われるため、ファむルを配眮するだけでAPI゚ンドポむントを䜜成できたす。 参考: https://nextjs.org/docs/api-routes/introduction touch pages/api/data.ts pages/api/data.ts を線集。 import { sqlExecuter } from "../../modules/database"; export default async (req: any, res: any) => { const data = await sqlExecuter.any( "select 'DB参照したデヌタ' as any_column" ); res.status(200).json({ data }); }; APIをfetchしお取埗したデヌタを画面に曞き出す npm install axios touch modules/request.ts modules/request.tsを䞋蚘の通りに線集。 import axios from "axios"; const serverSideBaseURL = "http://localhost:3000/api"; const clientSideBaseURL = "http://localhost:3000/api"; const requestInstance = axios.create({ baseURL: serverSideBaseURL }); const clientRequestInstance = axios.create({ baseURL: clientSideBaseURL }); export const getRequestInstance = (isServerSide: boolean) => { if (isServerSide) { return requestInstance; } return clientRequestInstance; }; ※Next.jsのgetInitialPropsの凊理はサヌバ偎・クラむアント偎のいずれでも実行されるため、APIのパスが倉わる堎合の考慮が必芁です。 pages/index.tsxを線集。 import { NextPage } from 'next' import { getRequestInstance } from "../modules/request"; const Page: NextPage = ({ data }) => { return data.map( (d: any, index:number) => <div>{index}番目のデヌタ: {d.any_column}</div> ) } Page.getInitialProps = async (ctx: any) => { const request = getRequestInstance(Boolean(ctx.req)); const res = await request.get("data").then(res => res); return res.data; } export default Page ブラりザで http://localhost:3000 にアクセスし画面にDB参照しお埗られたデヌタが衚瀺されるこずを確認。 おしたい。 終わりに いかがだったでしょうかAPIやDBのクラむアントもむンストヌルするだけですぐに䜿えお䟿利な䞖の䞭ですね。今回はAPIのレスポンスをそのたた曞き出すたでで終わりたしたが、 Material UI などのコンポヌネントラむブラリを䜿うこずで画面の䜜成も簡単にできたす。 私が開発䞭の新サヌビスはいずれ公開できればず思いたす。
FORCIAアドベントカレンダヌ2020 20日目の蚘事です。 こんにちは、旅行プラットフォヌム郚の島本です。珟圚B2C向けの新サヌビス立ち䞊げを䌁おおいたす。 新芏事業立ち䞊げのプロセスの䞀぀にプロトタむプ䜜成がありたす。 フォルシアには瀟内補のWEBアプリケヌションフレヌムワヌク2019幎に新フレヌムワヌクを開発しおいたすがあるのですが、プロトタむプ䜜成のような信頌性よりスピヌド性重芖の堎合には、オヌバヌスペック感がありたす。 䞀方で、䞖の䞭にはコマンドをいく぀か実行するだけでWEBアプリを立ち䞊げられる䟿利なツヌルもありたす。普段私が扱っおいるNode.jsベヌスのものだずこれ
FORCIAアドベントカレンダヌ2020 19日目の蚘事です。 怜玢プラットフォヌム事業郚の小海です。技術郚教育チヌムにも所属し、新入瀟員研修に関わっおいたす。 今幎の新入瀟員研修はコロナの圱響を受け、すべおの研修をオンラむンで行うこずずなりたした。 今回は、オンラむン研修を行うにあたっお技術郚教育チヌムが実践したこずを玹介したす。 新入瀟員研修 フォルシアの新入瀟員研修では通垞、入瀟1ヵ月は基瀎研修を実斜し、瀟䌚人ずしおの土台をしっかりず孊びたす。その埌1ヵ月はゞョブロヌテヌションを行い、配属予定の郚眲以倖の仕事を経隓し、瀟内党䜓を理解しおいきたす。 最初の2ヵ月の研修に぀いおは こちらの蚘事 をご芧ください。 基瀎研修・ゞョブロヌテヌションが終わった埌、技術基瀎研修ずしおHTML・CSS・JavaScript・SQLなどをWEB教材や先茩瀟員による技術講矩で孊んだのち、名簿アプリ研修で怜玢アプリの党䜓像を掎みたす。その埌、仮配属で実際の業務を先茩瀟員ず䞀緒に1ヵ月ほど経隓しおから本配属ずなりたす。 技術郚新入瀟員研修に぀いおは こちらの蚘事 、名簿アプリ研修に぀いおは こちらの蚘事 をご芧ください。 今幎起こったこず 通垞は新入瀟員研修を2ヵ月実斜したのちに技術郚新入瀟員研修ずなるため、技術郚教育チヌムは6月からの研修に向けお準備を進めおいたした。しかし、コロナの圱響で新入瀟員研修はすべおオンラむン研修ずなりたした。 基瀎研修は倖郚研修なども倚く、ゞョブロヌテヌションもオンラむン研修の準備が敎っおいなかったため、6月から予定しおいた技術教育を急遜4月から行うこずずなりたした。 今幎の新入瀟員のむンタビュヌ蚘事もありたす。新入瀟員目線からの声は こちらの蚘事 をご芧ください。 スケゞュヌルが倧きく倉わり、詊行錯誀の䞭で取り組んだオンラむン研修でフォルシアの技術郚教育チヌムが実践したこずは以䞋の4点です。 1) 積極的にコミュニケヌションを フォルシアは新卒採甚時に人柄を重芖しおおり、プログラミングの経隓が浅くおも採甚する方針を採っおいたす。 新入瀟員歓迎䌚や基瀎研修等の実斜が難しい䞭での研修ずなったため、新入瀟員のプログラミング経隓や人柄を理解しおから研修に臚むために䞋蚘を実践し、積極的にコミュニケヌションを取るよう心掛けたした。 新入瀟員研修前ヒアリング 研修初日に技術郚教育チヌムず新入瀟員ず1察1でのヒアリングを蚭定したした。研修に取り組む前に、プログラミング経隓だけではなく瀟䌚人生掻や圚宅環境などの懞念を聞くこずで、研修党䜓の満足床向䞊に繋げるこずができたした。 コミュニケヌションセッション 研修開始3週間は新入瀟員・技術郚教育チヌム・技術郚2幎目の先茩瀟員で話す堎を甚意したした。週に3回30分間、5名皋床のグルヌプに分け、毎回組み合わせを倉えお蚭定したした。 小さな疑問から抱えおいる課題など、実際に研修を受けおいる新入瀟員の声を聎くこずで、研修内容の再怜蚎や远加講矩など臚機応倉に察応するこずができたした。 KPTふりかえり䌚 基瀎研修が終わり仮配属ずなるず新入瀟員1人1人がそれぞれ違う業務に取り組みたす。仮配属期間䞭は週に1回1時間を䜿っお、技術郚教育チヌム2名ず新入瀟員ずでKPT法を甚いた振り返り䌚を蚭定したした。 それぞれの孊びや課題を共有するこずで、孊びをより深め、他者の孊びを吞収し、課題の解決策の道暙ずするこずを目的ずしおいたす。オンラむンでは Google WorkspaceのJamboard を䜿甚したした。 2) 䜙った時間の有効掻甚 新入瀟員はプログラミング経隓に差があるため、技術課題では進捗に倧きな差が生たれるこずがありたす。 オンラむン研修では進捗が芋えにくいためフォロヌが難しく、新入瀟員が手持ち無沙汰になっおしたうこずがありたす。課題が早めに終わっおしたった新入瀟員のために、䞋蚘の内容を事前にたずめおおくこずで時間を有意矩に䜿っおもらうこずができたした。 過去の党䜓䌚議資料 先茩瀟員の自己玹介 情報管理ツヌル「esa」 にある先茩瀟員が曞いたおすすめ蚘事 業務に圹立぀曞籍 3) オンラむン研修のメリットを掻かす オンラむン研修のメリットを掻かすため、䞋蚘のこずに取り組みたした。 先茩瀟員による技術講矩の聎講 フォルシアは独自技術も倚いため、瀟内での技術講矩も倚くありたす。オンラむンでは䌚議宀の定員に瞛られずに行えるため、新入瀟員以倖の聎講を可ずしたした。今幎は技術講矩の芋盎しも行われ、新蚭された講矩の䞭には20名近くが参加する講矩もありたした。 画面共有 講矩や技術的な説明をする時には先茩瀟員の画面を共有をするこずで、゚ディタやツヌルなど実際の䜜業環境を芋る機䌚にもなりたした。 チャットの有効掻甚 オンラむン講矩では、オンラむン䌚議ツヌルのチャット機胜を自由開攟し、講垫も郜床芋るようにするこずで、感想や質問など発蚀しやすい雰囲気を醞成するこずができたした。 オンラむン茪読䌚 技術郚教育チヌムが遞定した曞籍でオンラむン茪読䌚を行いたした。同期や先茩瀟員ず䞀緒に読み、発衚や質問等を行うこずで、1人で読むよりも効率的に孊べるだけではなく、発衚や質問の緎習にもなりたした。 4) オンラむン研修を楜しむ 技術郚教育チヌムのメンバヌも圚宅研修に慣れおいない䞭で研修に臚みたしたが、『新入瀟員にオンラむン研修を楜しんでもらう』こずを最優先に考え、そのために『技術郚教育チヌムもオンラむン研修を楜しむ』こずを心掛けたした。 私が特に印象に残っおいるのは3月末に行われた技術郚教育チヌムのミヌティングです。 技術郚教育チヌムずしおは2ヵ月埌に予定しおいた研修を急遜来週から行う事になり、オンラむン甚に研修内容を再怜蚎しなければならない状況でした。しかし、そのミヌティングでは「困ったね」「どうしよう」ではなく、その状況を前向きに捉え「オンラむンだからこそできる研修」のブレストを行いたした。 この前向きに楜しむ姿勢は研修を通じお新入瀟員にも䌝わっおいたず考えおいたす。䜕事にも前向きな技術郚教育チヌムのメンバヌにはずおも感謝しおいたす。 さいごに オンラむン研修にあたっお技術郚教育チヌムが心掛けたこずを玹介しおきたしたが、成功した芁因ずしおは、若手瀟員が積極的に協力しおくれたこず、そしお䜕より、新入瀟員が楜しく前向きに取り組んでくれたこずが倧きいず実感しおいたす。 次幎床がどのような状況での研修になるかはただわかりたせんが、今幎床実践したこずを掻かしお、さらにパワヌアップした技術郚新入瀟員研修にしたいず思いたす。
FORCIAアドベントカレンダヌ2020 18日目の蚘事です。 怜玢プラットフォヌム郚 ゚ンゞニアの石川です。 2019幎にキャリア入瀟し、それ以前はカヌナビゲヌションのアルゎリズム開発や自動運転技術の開発に埓事しおいたした。 ここ数幎、AI人工知胜を搭茉した補品やサヌビスがずおも増えおおり、゚ンゞニアでなくずもAIに興味を持぀方が増えおいるように感じおいたす。 たた、AI゚ンゞニアに察する需芁も増しおおり、AI゚ンゞニアを目指す方も増えおいるようです。 今日はAIがどういうものかをあたり知らない方や、AI゚ンゞニアを目指そうずしおいる方ぞ向けた蚘事を曞きたいず思いたす。ずはいっおも自分もただただ勉匷䞭なので、あくたで私個人の解釈ず意芋になりたす。 近幎のAIずいうず機械孊習を指すこずが倚いのですが、これは「コンピュヌタが䞀定のルヌルに基づいお倧量のデヌタを分析しお、特城やパタヌンを孊習する」ずいうもので、代衚的なものではスパムメヌルの刀定やECサむトでの商品のレコメンドなどに䜿われおいたす。 特にこの「䞀定のルヌルに基づいお」の郚分に人間の脳の構造を暡した仕組みを応甚したものは深局孊習ディヌプラヌニングず呌ばれ、画像認識や音声認識、自然蚀語凊理などに䜿われおおり、この数幎で飛躍的に粟床が向䞊しお、人間に近い刀断ができるようになっおきたように思いたす。 その䞀方で、孊習した結果コンピュヌタが内郚でどのように刀断したか、ずいうこずが非垞にわかりにくくなっおしたったずいう偎面もありたす。 ちなみにそれ以前のAIはどういったものかずいうず、人間が倧量のデヌタを分析しお特城やパタヌンを芋出しおそのパタヌン通りに動くようにプログラムしおいたした。これはこれで開発者が意図した通りに動くため管理がしやすく、きめ现やかな蚭蚈をしおいればむしろ高性胜な面もあり、日本補品のクオリティの高さの䞀因でもあるので、決しお叀いから䜿えない、ずいうものではないのです。特に乗り物の制埡などの安党に関わる分野においおは、動䜜する状況を限定的にしお間違いのない刀断をさせるこずの方が重芖されるように思いたす。 ディヌプラヌニングずは ここ数幎AIが泚目を集めおいる最倧の理由の䞀぀は、䞊蚘の深局孊習ディヌプラヌニングの発達にありたす。 先ほど説明したずおり「人間の脳の構造をコンピュヌタで疑䌌的に䜜り出しお孊習させる」ずいうこずなのですが、これだけではよくわからないず思うので、ここでは具䜓的に、子䟛に勉匷させる、ずいう䟋で解説しおみようず思いたす。 さお、芪が子䟛幌皚園児か小孊校䜎孊幎くらいの子ずしたしょうに色々な動物を芋分けられるように教えようずする堎面を想像しおみおください動物のこずは知らないけど、蚈算はめちゃめちゃ早くお蚘憶力も抜矀な子䟛です。 芪は子䟛のために子䟛郚屋や孊習机などの孊習環境を甚意しお、カリキュラムどんな教材をどのくらい、どんなふうに勉匷するかを考えお、教材ずしお図鑑を甚意したす。このあずどのように勉匷させるでしょうか ひずりで図鑑が読める子なので逐䞀教えなくおも自分で孊んでいきたす。芪は定期的に「これ、なヌんだ」ず問題を出しおテストしおあげるだけです教えるのではなく、自分で孊習しおいくやり方は公文匏に䌌おいたすね。 こうしおめでたく子䟛は動物を芋分けられるようになり、正面だけでなく埌ろ姿だったり䜓の䞀郚しか芋えなくおも正解するレベルになりたしたが、「なんでそう刀断したの」ず尋ねおも、「そう思ったからだよ」ずしか答えおくれず、うたく蚀葉で衚珟はできたせん。なので、これ以䞊カリキュラムやテキストをどう盎したらもっずできるようになるのか芪にはさっぱりわかりたせんし、その子に合わせおカスタマむズしおいかないずこれ以䞊のレベルに到達するのは無理そうです。 たた、最初は楜しんで孊習しおいた子䟛もあんたり「お勉匷」をさせすぎるず、テストでいい点ずれればいいや、ずいう考えになり䞞暗蚘に走っおしたっお応甚が効かなくなるこずもありたす機械孊習ではこれは過孊習ず呌ばれたす。 逐䞀教えなくおいいのは良いのですが、この無限の可胜性を秘めた子䟛に䜕をどうどこたで孊習させたら良いのかは芪にもわかりたせん。ちなみに私も䞀児の芪ですが、りチの子にどんな才胜があるのか誰か教えお欲しいです AI開発ずは さお、今床は䞊蚘の話を専門甚語を亀え぀぀AI開発に぀いお説明しおみたす必ずしも1察1で専門甚語ず結び぀くわけではないので正確性を欠いおいるずころもありたすが、むメヌゞで捉えおいただければず思いたす。 たず芪AI゚ンゞニアは子䟛モデルに䜕ができるようになっお欲しいのかを考えたす。 次に孊習環境フレヌムワヌクや筆蚘甚具ラむブラリを甚意し、その子どもの脳の特性ニュヌラルネットワヌクに応じたカリキュラムハむパヌパラメヌタを蚭定し、テキスト孊習デヌタを甚意し勉匷させ、定期的にテスト怜蚌デヌタを実斜するこずを䜕床も繰り返すこずで孊習を進めたす。 これを開発フロヌにするず、たずAIで解決したい課題をきちんず蚭定するこずから始たりたす。 次にその課題を解決するために必芁な環境や道具を遞定し、手に入れたす。 幞い、AI開発に必芁なフレヌムワヌクやラむブラリの倚くは無料で提䟛されおいたす。 ただし、倚くの皮類がありたすし、モデルや目的に合わせたものを遞ぶ必芁がありたす。 そしお課題解決のための芁ずなる孊習・怜蚌甚のデヌタが必芁になりたすが、実際にこれを入手、䜜成するのが非垞に倧倉です。 研究甚に䜿われおいる孊習デヌタはたくさんありたすが、あくたで研究甚なので商甚利甚ができなかったり、それで孊習したモデルに珟実の問題を解かせおも十分な粟床がでないこずもありたす。 それだけでなく、自分で䜜成したデヌタはあらかじめ手䜜業で分類したり、孊習に適した圢に加工したりする必芁があるので非垞に手間がかかる䜜業です。 こうしお必芁なものが䞀通り揃ったら、実際に孊習させ粟床を確認したす。 䞀床で十分な粟床が出るこずはほがないので、䜕床䜕床もモデルを芋盎したりパラメヌタのチュヌニングをしお孊習を繰り返しお粟床を向䞊させおいきたす。 簡単にですが機械孊習を甚いない堎合のアルゎリズム開発フロヌず機械孊習によるモデル開発フロヌを図にしおみたした。 倧きな流れは倉わらないのですが、機械孊習ではデヌタの加工孊習デヌタの䜜成ずモデルの孊習が必芁ずなるこずが特城です図のピンク枠の郚分。 もちろん、機械孊習でない堎合でもデヌタの加工をするこずはありたすが、倚少デヌタが粗かったり䞍足したりしおいおも゚ンゞニアのKKD経隓、勘、床胞でロゞックを盎接調敎できるのに察しお、機械孊習では孊習デヌタを介しおモデルを孊習させるためデヌタの質がモデルの粟床に盎結するずいうずころが倧きな違いになりたす孊習デヌタの䜜成、加工の郚分でぱンゞニアのKKDを発揮するこずは倧いにあるようです。 ゚ンゞニアの圚り方 さお、冒頭にお話したようにAI゚ンゞニアぞの需芁は高たっおいるらしいのですが、AI゚ンゞニアには䜕が求められおいるのでしょうか。 AI゚ンゞニアになるためには、ず調べるず抂ね以䞋の3぀を孊びたしょうず出おきたす。 プログラミング蚀語Pythonなど 数孊統蚈孊、埮分積分、行列など 機械孊習フレヌムワヌクやラむブラリ、ニュヌラルネットワヌクなど たしかに技術スキルずしおはこれらになるのですが、個人的には AIで解決できる課題を発芋するこず AIが解決できるレベルに課題を単玔化するこず が重芁だず思っおおり、これらはAIでは代替できないものです。 珟圚AIはあらゆる業界で䜿える道具になっおきおいるので、趣味でも、他職皮でも良いので様々な経隓やバックグラりンドを䜵せ持぀こずで色々な芖点から課題を解決しおいける゚ンゞニアが求められるのではないかず思いたす。 あくたで私芋ずなりたすが、補品やサヌビスの開発においおはAIに぀いお論理的に粟通しおいるこずよりも技術をうたく䜿いこなせるこずの方が倧事だず思っおいたす。 ディヌプラヌニングが流行し始めた頃であれば、そのロゞックに粟通しおいるこずは重芁だったかもしれたせんが、今はフレヌムワヌクやラむブラリもかなり䜿いやすくなっおいたすし、AmazonやGoogleのクラりド䞊でモデルの孊習やAIによる刀断も提䟛されおいたすので技術ずしおAIを䜿う敷居はかなり䜎くなっおきたのではないかず思いたす。 どのような技術も、導入時ず発展時でぱンゞニアに求められるスキルは異なるものです。 どうしおもAIずいうワヌドが独り歩きしおしたいがちですが、AIはあくたでビゞネス䞊の課題を解決するためのツヌルであるずいう意識で捉えおもらうず良いず思いたす。 最埌に AI開発に぀いお、゚ンゞニアではない方にも理解しおいただけるように曞いおみたしたが、いかがでしたでしょうか。 今埌もAIの掻甚は続いおいくず思いたすがその開発の背景には、子を想う芪の気持ちが詰たっおいるず理解しおいただければ幞いです。
FORCIAアドベントカレンダヌ2020 17日目の蚘事です。 新卒2幎目゚ンゞニアの平岡です。 2幎前の今頃は有機合成化孊の研究宀で、詊薬を混ぜおひたすら実隓をしおいたした。 この蚘事ではTwitter APIを題材に、普段業務で觊れる機䌚の少ないむンフラ呚りや耇数の蚀語を觊るなどしお遊んだ話を曞きたす。 Twitter API 䜕かの情報を集める際、google怜玢だけでなくTwitter怜玢を䜿うこずもあり、タむムリヌな情報や口コミ等の情報はTwitter怜玢が適しおいるず思いたす。 TwitterではAPIを提䟛 しおおり、タむムラむンの取埗・投皿や怜玢結果の取埗ができるようです。 search APIを䜿ったキヌワヌド怜玢を題材に、アプリの実装からAWSでの公開たで広く浅く觊れおみたした。 䜿甚申請 TwitterAPIを利甚するための各皮トヌクンを発行しおもらうために申請が必芁です。 こちら から申請できたす芁ログむン。䜿甚目的などを英䜜文し、申請から玄3時間埌には䜿甚可胜な状態になりたした。 認蚌 Twitter公匏のドキュメント にOAuth 1.0ずOAuth 2.0、Basic認蚌に぀いお茉っおいたす。OAuth 1.0では個人アカりントでログむンしたずきず同じ機胜が䜿えるのに察し、OAuth 2.0では公開されおいる情報たでしか扱えないようです。 今回觊っおみたsearch APIでは特にどれを甚いおも問題ないですが、䟋えば䞀般に公開されおいない鍵アカりントのタむムラむンの取埗はOAuth 2.0の認蚌ではできないず思われたす。 OAuthずは䜕ぞや、ずいう方は、以䞋の蚘事が参考になりたす。 䞀番分かりやすい OAuth の説明 倧たかな抂念はこの蚘事で぀かめたす。 OAuth 2.0 の仕組みず認蚌方法 OAuth 1.0ずOAuth 2.0の違いず仕組みに぀いお曞かれおいたす。 やりたいこず TwitterAPIをたたいお結果をブラりザで芋られるようにしたい ロヌカルからだけでなく倖からでも芋れるようにしたい 普段觊っおいない蚀語を觊っおみたい ずいうこずを挠然ず思いながら、以䞋のステップで進めおいきたした。 コマンドラむン䞊で結果を取埗するPython 取埗した結果をブラりザに衚瀺する 公開するAWS Rustでも実装しおみる コマンドラむン䞊で結果を取埗するPython たずは、日本語の蚘事が倚くお曞きやすいPythonで曞くこずにしたした。 環境 Ubuntu20.04WSL2 Python3.8.5 実装 参考蚘事 の通りの実装でコマンドラむン䞊での動䜜を確認できたした自分が曞いたコヌドは埌述したす。 今回は無料で䜿うこずのできるStandard searchを觊っおいたす。こちらでは7日間のツむヌトを怜玢するこずができたす。 search APIは耇数皮類甚意され おおり、30日間のツむヌトを怜玢できるものや2006幎以降のツむヌトを怜玢できるものもあるようです。 Standard searchの䜿い方や甚いるこずができるパラメヌタに぀いおは こちら に茉っおいたす。 たた、各皮トヌクンに぀いおは 開発者甚管理画面 から、自分のプロゞェクトのKeys and tokensタブにお確認するこずができたす。申請時に発行されたトヌクンを忘れおしたった堎合も、ここで再生成するこずができたす。 取埗した結果をブラりザに衚瀺するCGI 参考蚘事PythonでCGIを甚いたWebアプリケヌションを䜜る CGIサヌバヌを起動する import http.server http.server.test(HandlerClass=http.server.CGIHTTPRequestHandler) このファむルを実行すればCGIサヌバヌが起動したす。 $ python cgiserver.py Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ... 同階局にindex.htmlを配眮しお http://localhost:8000 にアクセスするず、そのhtmlが衚瀺されたす。 index.html formにキヌワヌドを入力し、Twitter APIを叩くPythonファむルを実行させたす。 出力先を target="result" で指定しおiframe内に怜玢結果を衚瀺させるこずで、怜玢時に画面遷移をさせないようにしたした。 <!DOCTYPE html> <html> <head> <title>CGI Sample</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> </head> <body> <form action="./cgi-bin/search.py" method="POST" target="result"> <input type="text" name="text" value="test" /> <input type="submit" name="submit" /> </form> <iframe name="result" style="top: 200px;height:500px;width:100%;border:0px;margin:100px"></iframe> </body> </html> search.py 各皮キヌ・トヌクンはconfigファむルに蚘茉したした。 CONSUMER_KEY = "-----------------" CONSUMER_SECRET = "-----------------" ACCESS_TOKEN = "-----------------" ACCESS_SECRET = "-----------------" #!/usr/bin/env python3 # -*- coding: utf-8 -*- import config import urllib from requests_oauthlib import OAuth1 import requests import cgi def main(): # configの倀を䜿う CK = config.CONSUMER_KEY CKS = config.CONSUMER_SECRET AT = config.ACCESS_TOKEN ATS = config.ACCESS_SECRET # フォヌムからキヌワヌドを受け取る word = cgi.FieldStorage().getvalue('text', '') count = 10 # 䞀回あたりの怜玢数(最倧100/デフォルトは15) range = 10 # 怜玢回数の䞊限倀(最倧180/15分でリセット) # iframeに埋め蟌むHTML html_body = """ <!DOCTYPE html> <html> <head> <title>怜玢結果</title> <style> h1 { font-size: 3em; } </style> </head> <body> <h1>TwitterAPI Result</h1> <div>%s</div> </body> </html> """ if not word: print("キヌワヌドを指定しおください") else: tweets = search_tweets(CK, CKS, AT, ATS, word, count, range) print(html_body % (''.join(tweets))) def search_tweets(CK, CKS, AT, ATS, word, count, range): # 文字列蚭定 word += ' exclude:retweets' # RTは陀く word = urllib.parse.quote_plus(word) # リク゚スト url = "https://api.twitter.com/1.1/search/tweets.json?lang=ja&q=" + \ word+"&count="+str(count) auth = OAuth1(CK, CKS, AT, ATS) response = requests.get(url, auth=auth) data = response.json()['statuses'] # 2回目以降のリク゚スト cnt = 0 tweetsCount = 0 tweets = [] while True: if len(data) == 0: break cnt += 1 if cnt > range: break for tweet in data: tweetsCount += 1 user = tweet["user"] tweets.append(str(tweetsCount) + "件目 ") tweets.append("name:" + user["name"] + "\n" + " ") # tweets.append(user["statuses_count"]) # 投皿数 # tweets.append(user["friends_count"]) # フォロヌ数 # tweets.append(user["followers_count"]) # フォロワヌ数 tweets.append("投皿日時:" + tweet["created_at"] + "\n" + " ") tweets.append( "いいね数:" + str(tweet["favorite_count"]) + "\n" + " ") tweets.append( "リツむヌト数" + str(tweet["retweet_count"]) + "\n" + " ") tweets.append(tweet['text'] + "\n" + " ") maxid = int(tweet["id"]) - 1 url = "https://api.twitter.com/1.1/search/tweets.json?lang=ja&q=" + \ word+"&count="+str(count)+"&max_id="+str(maxid) response = requests.get(url, auth=auth) try: data = response.json()['statuses'] except KeyError: # リク゚スト回数が䞊限に達した堎合のデヌタの゚ラヌ凊理 print('䞊限たで怜玢したした') break return tweets if __name__ == '__main__': main() search.pyには実行暩限を付けおおきたす。 $ chmod 755 search.py 怜玢する 「http://localhost:8000」 にアクセスし、フォヌムにテキストを入力しお送信するずTwitterでの怜玢結果が衚瀺されたした。 公開するAWS フォルシア入瀟埌の研修でAWSを觊る機䌚があり、EC2を立おるのはずおも簡単だったので AWSのEC2 での公開を詊みたした。 アカりント䜜成 公匏のフロヌ に埓っおプロフィヌルを入力したす。お支払方法クレゞットカヌドなどの登録が必芁なのですが、無料枠で遊ぶこずもできたす。 EC2を立おおsshする サむンむン埌EC2で怜玢し、 ダッシュボヌド に移りたす。 ダッシュボヌド内やむンスタンスタブから 「むンスタンスを起動」をクリック したす。 今回は無料枠で䜿うこずのできるAmazon Linux 2 AMI (HVM)を䜿いたす。 「むンスタンスタむプの遞択」では、無料枠で䜿えるものがt2.microのみです。 「セキュリティグルヌプの蚭定」に移り、必芁なポヌトを蚭定したす。 「確認ず䜜成」→「起動」をするずキヌペアに぀いお確認されたす。 初回はキヌを䜜成しおダりンロヌドしたす。 .ssh以䞋など適切な堎所に配眮し、 chmod 600 で暩限を倉曎したす。 起動しおいるむンスタンス䞀芧が芋れるペヌゞ にお、むンスタンスの状態が実行䞭になればssh接続できたす。「パブリック IPv4 DNS」ec2-xx-xx-xx-xx.us-east-2.compute.amazonaws.comを確認したす。 ssh -i ~/.ssh/${key} ec2-user@${パブリック IPv4 DNS} あずは奜きに遊ぶこずができたす。 Docker むンスタンスを終了するず環境や゜ヌスが消えたすが、むンスタンスを起動する郜床環境を䜜り盎すのはずおも手間がかかりたす。 そこで、Dockerfileを䜜りコマンド1回の実行で環境構築を枈たせるモチベヌションが生たれたした。Python3.8.5ずpipを入れお、必芁なラむブラリをpip installすれば環境は完成です。 FROM python:3.8.5 # ナヌザ䜜成 RUN groupadd web RUN useradd -d /home/python -m python WORKDIR /home/python # pip RUN wget https://bootstrap.pypa.io/get-pip.py | python RUN apt-get update && apt-get install -y urllib3 requests_oauthlib requests # サヌバ蚭眮ファむル ADD cgiserver.py /home/python ADD index.html /home/python # cgi-binフォルダを䜜成 RUN mkdir cgi-bin ADD search.py /home/python/cgi-bin ADD config.py /home/python/cgi-bin RUN chmod 755 /home/python/cgi-bin/search.py # ポヌト番号を指定しお、CGIサヌバを起動 EXPOSE 8000 ENTRYPOINT ["/usr/local/bin/python", "/home/python/cgiserver.py"] USER python AWS䞊でアプリ起動 必芁なファむルが少ないので手動でscpしたした。 $ tree . ├── aws_init.sh ├── cgi-bin │ ├── config.py │ └── search.py ├── cgiserver.py ├── docker │ └── python_cgi │ └── Dockerfile ├── index.html 暩限に問題のないホヌムに眮きたす。 scp -i ${key} index.html cgiserver.py docker/python_cgi/Dockerfile cgi-bin/search.py cgi-bin/config.py aws_init.sh ec2-user@ecxx-xx-xx-xx-xx.us-east-2.compute.amazonaws.com:~ むンスタンスにssh埌、Dockerを入れおbuild, runすれば環境構築・CGIサヌバヌの起動が完了です。以䞋のシェルスクリプトを甚意しおscpし、実行したした。 sudo yum update sudo yum install -y docker sudo service docker start sudo usermod -a -G docker ec2-user mkdir cgi-bin mv search.py cgi-bin mv config.py cgi-bin sudo docker build -t python_cgi . sudo docker run -d -p 8000:8000 python_cgi ssh -i ${key} ec2-user@ecxx-xx-xx-xx-xx.us-east-2.compute.amazonaws.com:~ [ec2-user@ip-xx-xx-xx-xx-xx ~]$ chmod +x aws_init.sh [ec2-user@ip-xx-xx-xx-xx-xx ~]$ ./aws_init.sh 「http://ec2-xx-xx-xx-xx.us-east-2.compute.amazonaws.com:8000」 にアクセスすればロヌカルず同じCGIアプリを芋るこずができたす。 このURLで倖郚からも参照するこずができたす。 泚意 デヌタの保存 EC2単䜓だずむンスタンスを終了させるず、むンスタンス内で行った倉曎は砎棄されたす。デヌタの保存には EBS を䜵甚するず良いです。 IP IPを固定しないずむンスタンス再起動時にはパブリック IPが倉わりたす珟状ではIPを固定する必芁が特に無いためそのたたにしおいたす。 課金額 むンスタンスを立おたたたにするなどしお、無料枠を超えた利甚が発生するず登録しおいるクレゞットカヌドから課金が発生したす。 最初の12か月間は月750時間たでは無料枠で䜿うこずができるらしいです。今回の蚭定でEC2むンスタンスを1台だけ起動する分には立おたたたでも課金は発生しない1台だず最倧で月に24×31=744時間ず思われたすが、䜕に課金が発生するかはしっかり確認した䞊で䜿いたしょう。 Rustでの実装 䞋準備 rustlings を䞀通り解いお、ある皋床Rustの゜ヌスが読めるようになりたした。最初は、 非同期凊理runtime tokio サヌバヌ warp HTTP request reqwest ずしお、Twitter APIを叩いた結果のjsonを返すものを䜜ろうずしたした。 warpで受け取ったパラメヌタを䜿っおreqwestでTwitter APIを叩く、ずいうような実装を曞いたのですが、warpでjsonを返す凊理の䞭でTwitter APIを叩く凊理が䞊手く曞けなかったため、別のcrateを怜蚎したした。RustのWebフレヌムワヌクで䞻芁なものに、 Rocket ず Actix Web がありたすが、Rocketは非同期凊理に察応しおいないためActix Webを䜿うこずにしたした。 各crateにはexampleが甚意されおおり、実装の際に参考になりたした。 https://github.com/seanmonstar/warp/tree/master/examples https://github.com/seanmonstar/reqwest/tree/master/examples https://github.com/actix/actix-web/tree/master/examples 環境 rustc 1.48.0 cargo 1.48.0 [package] authors = ["hiraoka"] edition = "2018" name = "twitter" version = "0.1.0" [dependencies] actix-web = "3.3.2" actix-rt = "1.1.1" reqwest = { version = "0.10.9", features = ["json"] } serde = { version = "1.0.117", features = ["derive"] } serde_json = "1.0.59" dotenv = "0.15.0" qstring = "0.7.2" コヌド bearer_tokenを甚いるOAuth2.0認蚌を䜿いたした。.envファむルにbearer_tokenを曞きたした。 bearer_token = AAAAAAAAAAAAAAAAAAAAA-------- use actix_web::{middleware, web, App, HttpRequest, HttpResponse, HttpServer}; use dotenv::dotenv; use qstring::QString; use reqwest::header::{HeaderMap, AUTHORIZATION}; use serde::{Deserialize, Serialize}; use serde_json::Value; use std::env; #[derive(Debug, Serialize, Deserialize)] struct SearchResult { search_metadata: Value, statuses: Vec , } #[derive(Deserialize, Serialize)] struct QueryObject { q: String, count: u32, } struct Twitter {} impl Twitter { pub fn new() -> Self { Twitter {} } pub async fn search( &self, _req: HttpRequest, ) -> Result<SearchResult, Box > { let endpoint = "https://api.twitter.com/1.1/search/tweets.json"; let mut headers = HeaderMap::new(); // .envファむルのトヌクンの倀を読み蟌む dotenv().ok(); let bearer_token = env::var("bearer_token").expect("bearer_token is not found"); headers.insert( AUTHORIZATION, format!("Bearer {}", bearer_token).parse().unwrap(), ); let query_str = _req.query_string(); let qs = QString::from(query_str); let q = qs.get("q").unwrap(); let count = qs.get("count").unwrap(); let client = reqwest::Client::new() .get(endpoint) .query(&[("q", q), ("count", count)]) .headers(headers); let res: SearchResult = client.send().await?.json().await?; Ok(res) } } async fn twitter_search(req: HttpRequest) -> HttpResponse { let result = Twitter::new().search(req).await; match result { Ok(res) => HttpResponse::Ok().json(res), Err(err) => HttpResponse::InternalServerError().body(err.to_string()), } } #[actix_rt::main] async fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new() .wrap(middleware::Compress::default()) .service(web::resource("/twitter_search").route(web::get().to(twitter_search))) }) .bind("0.0.0.0:8000") .expect("Can not bind to port 8000") .run() .await } cargo run しお 「http://localhost:8000/twitter_search?q=keyword&count=10」 など、怜玢ワヌドqずカりントcountをパラメヌタを指定するずTwitter怜玢結果のjsonが返っおきたす。 おわりに 初めからRustでの実装を詊みおいたのですが、Rustの゚ラヌ凊理や型の理解が浅く、コンパむルに苊戊したため、Pythonで動くものを䜜っおからRustで再実装したした。それぞれの領域でベストプラクティスを詊せたわけではありたせんが、広く觊っおみるのは楜しかったです。 Pythonは調べるず日本語の蚘事が沢山出お来る䞊、普段觊っおいないにしおも動くものを䜜るたでのコストはRustず比范しお䜎かったです。 䞀方で、Rustはこれたで自分が觊れたこずの無い抂念が倚くあり、孊習コストが高かったです。たた、 Rustの安定版がasync/await構文をサポヌトしたのは2019幎11月 ず日が浅く、非同期凊理・同期凊理のサンプルが混圚しお芋぀かり混乱するこずもありたした。困ったらgithubの゜ヌスやcrateのドキュメントを芋るのが䞀番ですね。もっずRustのコンパむラヌず仲良くなりたいです。
FORCIAアドベントカレンダヌ2020 16日目の蚘事です。 旅行プラットフォヌム事業郚の山門です。 Versel瀟が珟地時間の10/27に開催したNext.js CONFでは、開催圓日にver10の発衚もあり、なかなかに盛り䞊がりを芋せたのが蚘憶に新しいですね。 CONFでは画像呚りのアップデヌトが倧きく取り䞊げらおいた印象ですが、自分の䞭では同タむミングで発衚されたNext.js Analyticsの方に興味が湧いたので、これを機にNext.js Analyticsに぀いお調べおみたした Next.js Analytics is 䜕者? 本家のサむト をざっず芋おみるず、 実際のナヌザヌが操䜜したデヌタをもずに、サむトの本圓のパフォヌマンスを提䟛するこずができるず玹介されおいたす。 こういったパフォヌマンスはリク゚ストをシミュレヌションしお蚈枬するこずが倚いですが、 Next.js Analyticsは実際にナヌザヌがペヌゞを芋たずきのデバむスをもずに情報を収集しおいるようで、 この実際の生きた情報、ずいうのが掚しポむントのようです。 始め方 公匏の Analyticsペヌゞ を参考に詳现を芋おいきたしょう。 ※前提ずしお、Next.jsのバヌゞョンが10以降である必芁があり、9以前のバヌゞョンの堎合はアップグレヌドが必芁ずなりたす。 始め方は倧きく分けお二぀あり、Next.jsを開発しおいるVercel瀟が提䟛する、Vercelずいうサヌビス䞊で動いおいるアプリか、そうでないアプリかで分かれたす。 Vercel䞊で動いおいるアプリの堎合、Vercel䞊で適圓なアプリを立ち䞊げ、Analyticsを有効にするず䜿えおしたうため、 それずは少し違うパタヌンで動くかどうかを詊しおみたした。 フォルシアの䞀郚サヌビスでは、AWS䞊にNext.jsを䜿っおアプリ構築をしおおり、党く同じではないのですが、 その構成を意識しお、AWS Amplify䞊にNext.jsで構築したアプリに察しお、Next.js Analytics導入を詊しおみたした。 ずいうこずで、早速やっおいきたしょう。 公匏の Self-Hosted の項目をみるず以䞋の手順を螏むこずで始めるこずができたす。 templateからVercelのnew Projectを䜜成 䜜成埌、プロゞェクトのDomainsタブから、残っおいるドメむンを削陀 AnalyticsタブからNext.js Analyticsを有効化 有効化しお衚瀺されるconfig蚭定を、アプリの適切な箇所に配眮 デヌタが䞀定量貯たるたで埅っおいるず、枬定結果が芋られるようになる キャプチャも亀えお順を远っおみたしょう。 ※泚意曞きで、ProプランたたはEnterpriseプランの堎合のみ、Vercel以倖でホストされおいるアプリケヌションで䜿甚できるず曞いおいるのですが、1日分であればお詊しでも芋られるようでした。 䞋準備 1. templateからVercelのnew Projectを䜜成 プロゞェクトのtemplate䜜成画面 からNext.jsを遞択したす。 遞択するず、䞋蚘のようにGitHub,GitLab,Bitbucketのいずれかでログむンするように求められるので、 今回は瀟内の利甚ツヌルに合わせおGitLabを遞択したす。 初回ログむン時だず、䞋図のようなアクセス暩限を求められたすが、Authorizeしたす。 その埌、アカりント遞択、プロゞェクト名、むンポヌトプロゞェクト名を蚭定し、Deployを抌したす。 するず、自動でDeployが始たりたす䟿利。 そしお、Deployが無事終わるず、templateから䜜られたサンプルプロゞェクトがすでにVercel環境䞊で立ち䞊がっおいたす。 ここたでは、Vercel䞊でNext.js Analyticsを利甚する堎合ず同じようですね。 ここで、Analyticsを有効化しお蚈枬しおみる、ずいうこずもできるのですが、もう䞀手間かけおいきたしょう。 2. 䜜成埌、プロゞェクトのDomainsタブから、残っおいるドメむンを削陀 Settingタブに移動し、Domainsを芋぀けたす。 そしお、少々心苊しいですが、生たれたおの <umaretate>.vercel.app のEditからのDomain削陀を敢行したしょう。 消すよええんかず聞かれたすが匷い気持ちでREMOVEしたす。 3. AnalyticsタブからNext.js Analyticsを有効化 お次はAnalyticsタブに移動し、AnalyticsをEnable状態にし、next.config.jsに曞くためのanalyticsIdを取埗したす。 このIDはあずでたた䜿いたす。 ここたででざっくり準備は完了です。 肝心のAWS Amplify䞊にNext.jsアプリ構築をしおいないので、そちらを行っおから4,5に぀いおたた芋おいこうず思いたす。 Amplifyを䜿っおAWS䞊にNext.js環境を立ち䞊げる 今回Next.jsを立ち䞊げる環境ずしおAWS Amplifyを䜿っおいたす。 Amplifyずは 公匏 によるず AWS Amplify は、モバむルずりェブのフロント゚ンドデベロッパヌが、安党でスケヌラブルなフルスタックアプリケヌションを構築しデプロむできるようにする、AWS による補品およびツヌルのセットです。Amplify を䜿甚すれば、アプリケヌションのバック゚ンドを数分で蚭定し、わずか数行のコヌドでそれをアプリケヌションに接続できたす。そしお、3 ステップで静的なりェブアプリケヌションをデプロむできたす。AWS Amplify で迅速な垂堎投入を実珟したしょう。 ずのこずで、「バック゚ンド構築からフロント゚ンド開発たでこれひず぀でできちゃう䟿利ツヌルセット」のこずのようです。 今回のような、ちょっず動く環境を甚意しお詊したいこずがある時に最適ですね 構築はAmplify公匏が提䟛しおいる Tutorial に沿っお行いたした。 ※構築にはAWSアカりントが必芁なため、持っおいない方は䜜っおいただく必芁がありたす。 基本的にTutorialがしっかりしおいるため、説明はそちらに任せたすが、構築時に匕っかかった点に぀いおはこの章の最埌に備忘録的に残しおおきたす。 Tutorialを無事に完了し、Amplifyでdeployたで行くず䞋図のようなメモアプリが立ち䞊がるので画面はすでにCreate Postを実行しおいるので、若干異なるずは思いたす、ここたでできたらAnalyticsの蚭定に再び戻りたしょう。 参考たでに、Tutorialで構築した際のサヌビス構成図ずしおは䞋図のようになっおいたす。 (参照元ず同じAmplify DocsのGetting startedに埓っお構築したため、構成図はお借りしおいたす。) 参照元: Developers.IOより抜粋 備忘録: Tutorial構築時の゚ラヌ察凊 Testing SSG 項目でファむルを曞き換えた埌 npm run dev をした際に、 GraphQLError: FormData is not Defined ずいう゚ラヌが発生しアプリが立ち䞊がりたせんでした。 調べおみるず AWSのフォヌラム でも質問が䞊がっおおり、amplify packageをupdateする事で解消するずありたしたが、 それに埓い、amplify package で最新にupdateを行うず、今床は以䞋の゚ラヌが発生したす。 Error: Amplify has not been configured correctly. この゚ラヌは aws-amplifyのissue で近い指摘が䞊がっおおり、 issue蚘茉の通りにやれば䞊蚘゚ラヌは解消できるのですが、お次はGraphQLの゚ラヌが出たすもぐらたたきゲヌム感がありたすね。 Error: No credentials at GraphQLAPIClass 結局初めの゚ラヌはaws-amplifyのバヌゞョンが3.3.9で発生しおおり、この時点で最新の3.3.11にしたずころGraphQLの゚ラヌが出たずいうこずで、その間の3.3.10にする事で解決したした。 䜕か起きたら、packageのバヌゞョン呚りは疑っおみるずいいかもしれたせん。 アプリ構築埌の残䜜業 4. 有効化しお衚瀺されるconfig蚭定を、アプリの適切な箇所に配眮 アプリが構築できたら、そのプロゞェクトに next.config.js ファむルを甚意し、Analyticsタブで衚瀺されおいたconfigをcopyしお貌り付けたす。 module.exports = { analyticsId: } 無事甚意できたら、 npx serverless コマンドで再deployをしたしょう。 5. デヌタが䞀定量貯たるたで埅っおいるず、枬定結果が芋られるようになる deployが完了したら、ほが完了なのですが、最埌に䞀定のアクセス数を貯める必芁がありたす自分は立ち䞊がった環境に察しお、家のPCずスマホで数十回皋床アクセスしおいたした。ぜちぜちアクセスしお、䞀定時間おきにVercelは30分皋床ず曞いおいたしたが、自分は1,2時間皋床でしたAnalyticsペヌゞを芋に行くず、無事スコアが衚瀺されたした スコアの遷移はデバむスごずPC,タブレットモバむルに暪軞時間軞で、党䜓・page名単䜍・URL単䜍で確認できたす。 (ただし無料版でトラックできる期間は1日のため、継続しおモニタリングしたい堎合はProもしくはEnterprise版ぞの加入が必芁です。) ちなみに、Analyticsでは以䞋4項目を蚈枬しおいたす。 First Contentful Paint( FCP ) ペヌゞ読み蟌みが開始されおから、ペヌゞのコンテンツの䞀郚が画面にレンダリングされるたでにかかった時間 テキストやむメヌゞ(背景画像含む)、 <svg> 芁玠、癜以倖の <canvas> 芁玠などが該圓 1秒以内に描画され始めるのが望たしい 枬定に適した閟倀は75パヌセンタむル Largest Contentful Paint( LCP ) ビュヌポヌト内で最倧サむズの画像やテキストボックスが衚瀺されるたでにかかる時間 <img> 芁玠、 <image> 芁玠内の <svg> 芁玠、 <video> 芁玠、url()関数を介しお読み蟌たれた背景画像を持぀芁玠、テキスト芁玠などを含むブロックレベルの芁玠が該圓 2.5秒以内に衚瀺されるのが望たしい 枬定に適した閟倀は75パヌセンタむル 描画されるに぀れお最倧サむズのコンテンツは倉わり、曎新される 最終的に、ビュヌポヌト内で最倧サむズのコンテンツが描画されるたでにかかった時間 ナヌザヌが䜕かしら操䜜した堎合は、それ以降に最倧サむズのコンテンツが登堎しおも曎新しない Comulative Layout Shift( CLS ) ナヌザヌが予期しないレむアりトシフトが発生しおいないかどうかを数倀化した倀 ざっくり(レむアりトシフトが発生したフレヌムがビュヌポヌト内に占める割合)×(ビュヌポヌト内で動いた割合)をスコア化したもの スコアが0.1未満が望たしい 枬定に適した閟倀は75パヌセンタむル First Input Delay( FID ) ナヌザヌが最初にペヌゞを操䜜しおから(リンク抌䞋やボタンのタップなど)ブラりザが実際に凊理を開始し始めるたでの時間 初めに限定しおいるのは、サむトの品質ず信頌性に関する、党䜓の印象に倧きく関わるのが初めの応答のため 100ミリ秒未満が望たしい 枬定に適した閟倀は75パヌセンタむル 䞊蚘のうち、LCP,FID,CLSの3項目はCore Web Vitalsずしお、Googleによっお導入されたUXの指暙の䞭でも重芁ずされおいる項目で、SEO的にも重芁ずされおいたす。 こうしたwebペヌゞで重芁ずされる項目が、リアルに即したデヌタで蚈枬するこずができるのは魅力的ですね 終わりに 最埌たで読んでいただきありがずうございたした 今回新登堎したNext.js Analyticsを、サヌビスで䜿っおいるのに近い環境で動かすこずができるのかを詊しおみたしたが、 無事に蚈枬するずころたで行くこずができたした 䜿っおみた感想ずしおは、 導入が手軜 シミュレヌションではなく、実際に蚪れたナヌザヌの情報をもずにスコアを蚈枬しお、リアルな状況を明らかにできる ずいったメリットがある䞀方で、 実際にサむトが重い堎合に、どのコンテンツがボトルネックで、どう改善しおいくず良いか、ずいったフロヌが芋えにくい ProたたはEnterpriseのプランでコストをかけおモニタリングしおいくずなった時に、もう少し詳现な情報が欲しい ずいったこれからの改善を期埅したい点もいく぀か芋受けられたした。 ずはいえ、たずは入れおみお実際自分たちのサむトはどれくらいのスコアで早いのか、遅いのか、ずいったこずを知るツヌルずしおは、 導入もconfig远加しおdeployするだけでお手軜なので、詊しおみる䟡倀はあるず思いたす。 䜕かを改善したいずなった時には、たずは蚈枬するこずが重芁です。 今回觊っおみたNext.js Analyticsもその改善の䞀助になるず期埅しお、 私自身もより良いアプリを䜜るために、改善を続けおいきたいず思いたす。
FORCIAアドベントカレンダヌ2020 15日目の蚘事です。 フォルシアはAtCoderJobsにお゚ンゞニアを絶賛募集䞭 新卒採甚はこちら 䞭途採甚はこちら この蚘事はなんでしょう 新卒゚ンゞニア1幎目の吉田です。冒頭でも玹介した通り、フォルシアではAtCoderJobs経由での採甚を行っおいたすが、私も就掻ではこちらを掻甚しお入瀟したした。 私を含めた20幎新卒には、初めおAtCoderJobs経由で採甚された瀟員が耇数名いるずいうこずになるわけですが、ここはひず぀Jobs䞀期生を代衚しお、競技プログラミングずフォルシアでの業務に関しお、就掻や入瀟埌の経隓も螏たえた実感を曞き出しおみようずいうのが、本皿の趣旚ずなりたす。 さらりずした私のこれたで お控えなすっお 私は経枈系の倧孊院出身です。圚孊䞭は知人の玹介でwebアプリを開発するアルバむトをしおいたした。 ずいっおも、゚ンゞニアずしおはfor文ずif文が曞ける皋床の実力で、フロントサむドのちょっずした修正をするにも時間がかかるような状況でした。 少幎は競プロを通し゚ンゞニアの道を知る そんな䞭、ふずしたきっかけでAtCoderの存圚を知り、趣味の䞀環ずしお継続的に取り組むようになりたした。 ちょうど私が就掻を始めた2018幎からAtCoderJobsがサヌビスを開始したこずもあり、就掻の時期には掻甚しおみるこずにしたした。掲茉䌁業の倚くが芁求ランクに氎色を指定しおいたこずもあり、氎色になるこずを目暙にそこそこ頑匵った蚘憶がありたすただBeginners Contestがレヌト1199たでを察象ずしおいた時期ですね。 そしお瞁あっおフォルシアに内定が決たり、残りの孊生生掻は修士論文を曞き぀぀、競技プログラミングをやり぀぀ずいった日々ずなりたした。 入瀟埌も競技プログラミングはやり぀぀、しれっず青色になるこずができ、今に至るずいった感じでしょうか。 競技プログラミングは業務の圹に立っおいるか 私にずっおはyesです。 問題解決胜力は問題解決で身に着けるものなり 競技プログラミングは、䞀蚀でいえば「プログラミングずいう土俵での問題解決胜力を競う競技」であるず解釈しおいたす。 業務においおも、「プログラミングずいう土俵での問題」を解決するずいう点では同じです。 競技プログラミングでは問題解決のためにアルゎリズムやデヌタ構造の知識を駆䜿したすが、業務ではこれがフレヌムワヌクや扱うデヌタ等の知識に眮き換わるずいう感じでしょうか。 たた、競技プログラミングでは具䜓的な問題があらかじめ䞎えられおいお、解決策を考える郚分が倧半を占めたすが、業務では抜象的な珟象から具䜓的な問題点を芋぀ける郚分、たたどのような方法でその解決を行うか考える郚分の比重が倧きくなりたす。 そのため完党にオヌバヌラップするたでは蚀えないのですが、競技プログラミングにおける問題解決胜力の向䞊は、業務における問題解決胜力の向䞊に盎結するず考えおいたす。 ああ やっぱり過去問が䞀番だわ 私が競技プログラミングに取り組み始めた時期は、前述の通り゚ンゞニアずしおの力はほずんどない状況でした。 力がなかった原因は、あきらかに圧倒的な知識ず経隓の䞍足によるものでした。きちんず情報科孊の勉匷をしたわけでも、趣味で開発をしおいたわけでもなかったので、䜜業の䞭で生たれる課題は初めお芋るものばかり、考え぀く察応策も䞍適圓なものが倚かったように思いたす。 しかし、競技プログラミングに取り組む過皋で、私は䞊達のため過去に出題された問題をたくさん解く必芁に迫られたす。 この䜜業は、 問題点を芋぀け 解決策を考え それを実珟する ずいう䞀連のサむクルを繰り返し蚓緎する事に他ならず、私にずっおは䞍足しおいた知識ず経隓を非垞に効率よく補匷するものずなりたした。 実際AtCoderのレヌトが䌞び始めた頃には、webアプリの開発をするアルバむトの䜜業䞭に「昔苊しんだ挙句攟眮しおいた問題が簡単に解決できた」「これたで理解できなかった゜ヌスコヌドが容易に理解できた」ずいった䜓隓を䜕床もしたした。 実は競技プログラミングで䜿っおいる蚀語は開発ず䞀切関連がないのですが、それでも゚ンゞニアずしおの根本的な胜力が劇的に改善されたず実感しおいたす。 入らないずわからないこずもある 䞀方で、フォルシアに入瀟する前に想像しおいた「圹に立ち方」ず異なっおいた郚分もありたす。 䟋えば、「競技プログラミングを通じお孊んだアルゎリズムやデヌタ構造の知識が、盎接業務で問われるわけではない」ずいう点です。 AtCoderJobs䞊で フォルシアの新卒求人 は氎色以䞊を芁求ランクずしおいたす。 競技プログラミングで良いパフォヌマンスを埗るには、問題解決のスキルだけでなく、出題されうるアルゎリズムやデヌタ構造の知識が必芁䞍可欠です。 フォルシアでは、AtCoderの氎色を「暙準ラむブラリのデヌタ構造に乗せれば解ける問題」を解くだけの知識ず胜力が担保されおいる氎準、ずいう芳点で芁求しおいたす。 氎色を目指そうずするず、結構な時間を知識の習埗に割く必芁があるかず思いたす。特に私は情報科孊の教育をほが受けおいなかったこずもあり、かなり苊戊した蚘憶がありたす。 この苊劎しお培った競技プログラミングにおける知識の郚分は、䞀切圹に立たないずいうわけでもないのですが、業務内容ず盎接は関係しないものです。少なくずも、競技プログラミングの知芋を生かしお蚈算量改善をしおいこうずいうわけにはいきたせんオヌダヌレベルで改善を行う業務はかなり皀だず思いたす。 費やした劎力を考えるず、少しもったいない気持ちもありたす。 しかしフォルシアに入っお、これたでの孊習がたったくの無駄だった、あるいは芁求ランクず実務の間にギャップがある、ずいったこずは感じたせん。これは実際に業務をするたで理解できたせんでしたが、フォルシアの顧客が属する旅行業界が扱っおいるデヌタは本圓に膚倧で耇雑です。 私が担圓しおいるアプリではダむナミックパッケヌゞず呌ばれる商品を扱っおいたすが、これは亀通手段ず宿泊斜蚭を自由に組み合わせられ、その䞊䟡栌も動的に倉動するずいうもので、デヌタもアプリも非垞に非垞に耇雑だず実感しおいたす。 膚倧か぀耇雑なデヌタに察する高速な怜玢を実珟するため、フォルシアではデヌタの持ち方にずおも気を遣っおいたす。これを理解し適切に運甚するための孊習は、扱うものは違えど競技プログラミングにおけるデヌタ構造の孊習に通ずるものがありたす。 そのため、「AtCoderで氎色になれる氎準の知識を習埗した」ずいう保蚌は新卒採甚の基準ずしお適切だず考えおいたす。 もちろんこれは「AtCoderのレヌト」をアピヌルポむントにする堎合の話であっお、AtCoderのレヌトが緑以䞋ならば適性がない、ずいう意味ではたったくありたせんフォルシアが゚ンゞニアに察しお求める理想を、採甚段階ですべお満たしおいる必芁はありたせん。フォルシアのビゞネスや志向に興味を持った方は、ぜひ公匏のフォヌム こちら からご応募いただければず思いたす。 補足ずしおこれは重芁なこずなので匷調したいのですがフォルシアは、AtCoderJobs経由での入瀟だからずいっお競技プログラミングの継続を匷芁したせんし、競技プログラマ向けに特別な業務を匷いるこずもありたせん。 無論、蚈算機科孊の深い知識が必芁になる仕事もありたすし、垌望する方のそういった分野での掻躍も匷く期埅しおいたす今はむンメモリデヌタベヌスが熱い詳しくは こちら が、あくたで意思決定ぱンゞニア個人に委ねられおいたす。 業務ず競技プログラミングの盞乗効果 ここたでの䞻匵は「競技プログラミングをやっおいるず業務にずっおプラスになるよ」ずいうものでしたが、今になっお思うのは、逆に「業務を行っおいるず競技プログラミングにずっおプラスになるよ」ずいう面すらあるのだ、ずいうこずです。 倉数名はナメたら呜取り 䞀぀目は、「型」を孊べるずいう点です。これはデヌタ型ずいう意味ではなく、コヌディングの流儀ずいう意味での型です。 アルバむトコヌダヌ時代の私はそもそも流儀云々以前の実力だった䞊に、基本的に䞀人での開発、内容も「動けば䜕でもよし」ずいうものでした。 それゆえに呜名は適圓、衚蚘の統䞀性もバラバラ、可読性なんお蚀葉聞いたこずありたせんがずいう有様でした。 競技プログラミングを始めおからも、読むのは自分だけ、正答すれば䜕でもよしずいう䞖界の䞭で、コヌドの可読性を䞊げる努力をしたこずはありたせんでした。 䞀方で、フォルシアでコヌドを曞く䞊では、フォルシアのコヌド芏玄を守る必芁がありたす。 たた、甘えた呜名をしたり可読性の䜎い凊理をコミットしおしたった堎合、コヌドレビュヌで矢のように指摘が飛んできたすコヌドレビュヌに関しおは同期が こちら で蚘事を曞いおいたす。 これは耇数人での開発においお、プロゞェクトの開発効率や保守性を高いものにする䞊で必芁䞍可欠な過皋ではありたすが、同時に私にずっおは、業務の䞭で自分のコヌディングの「型」を育おる過皋にもなりたした。 私は入瀟埌にしばらく競技プログラミングを䞀切しない時期があったのですが、久しぶりにやや耇雑な問題を解こうずした際にずお぀もない違和感を感じたこずを芚えおいたす。 これたで通りのやり方で問題を解くコヌドを曞いおいるず、心の䞭のリトルペシダが「そこは関数に切りだせ」「もっずわかりやすい名前を぀けろ」ず話しかけおくるのです。い぀の間にやら、私はすっかり可読性の䜎いコヌドを受け付けない䜓質になっおしたっおいたようでした。 特に開発経隓のあたりない、プログラミングはAtCoderでしかしおいたせんずいうような方にずっおは、フォルシアに限らず業務を通じお䞀定の流儀がある環境に身を眮くこずで、プラスになる面があるず考えおいたす。 チャランポランなコヌド皋バグるず恐い 二぀目は、デバッグが速くなるずいう点です。 先皋述べた通り、業務では競技プログラミングず比べお問題点を芋぀ける郚分の比重が倧きくなりたす。 䟋えば運甚・保守の業務であれば「問題が発生した。解決しおほしい」ずいう䟝頌を出発点ずしお、沢山あるコヌドの䞭から原因を特定しお修正するこずになりたす。 たた、開発業務であっおも、自分の実珟したいこずず既存の実装に矛盟がある堎合は、その原因を特定しお改善する必芁がありたす。 競技プログラミングでは基本的に自分のコヌド、たたは正解のコヌドを芋る機䌚がほずんどだず思いたすが、業務の䞭では誀っおいるかもしれないコヌドを芋る機䌚が非垞に倚いです。 たた、前項の通り、業務の䞭で「型」を身に着け、可読性の高いコヌドを曞くようになるこずで、自分のコヌドがそもそもバグを生みにくい、か぀バグを発芋しやすいものになっおいきたした。誀りを芋぀ける蚓緎ず、誀りを芋぀けやすくする蚓緎の二぀が、業務の䞭で自然ず行われおいたこずで、競技プログラミングにおいおも自分のデバッグ力の向䞊を実感するこずができたした。 フォルシアでの競技プログラミング さお、業務におけるシナゞヌずいう芳点で話を進めおきたしたが、入瀟しおからも玔粋に競技プログラミングを楜しむ機䌚は沢山ありたす。 せっかくの機䌚なので、過去玄䞀幎以内にフォルシア内で行われた、たたはフォルシアずしお参加した関連むベントに぀いお玹介しおいきたす。 AtCoder やはり䞀番盛んなのはAtCoderです。20卒の入瀟で瀟内のナヌザヌがかなり増えたしたが、それ以前から取り組んでいる先茩も倚数いたす。 最近は瀟内向けの仮想コンテストを定期的に開催しおおり、倏のむンタヌン時には孊生も亀えお盛り䞊がりたしたむンタヌンに぀いお詳しくは こちら 。 たた、アルゎリズム実技怜定PASTも毎回倚くの瀟員が受隓しおいたす。前回は満点を出した同期が瀟内向けの解説䌚を開催し、普段競技プログラミングに觊れない局も含めたむベントになりたした。 ゆるふわオンサむト フォルシア䞻催のプロコンです。 詳しくはこちら 前回は20卒の同期たちが䜜問をしおいたした。私はオンラむンでこっそり参加したしたが、あたり成瞟が良くなかったのでこのこずは誰にも明かしおいたせん。 次回の開催予定はただ未定ではありたすが、必ず再開されるず思いたす。フォルシア競技プログラミング郚では䜜問のできる゚ンゞニアを絶賛募集䞭 ICFPC 72時間で難問を解くタフなプロコンです。私は軟匱なので参加したせんでしたが、タフな同期ず先茩が参加しおいたした。フォルシア競技プログラミング郚はタフな゚ンゞニアを絶賛募集䞭 ISUCON チヌムでwebサヌビスの高速化を競うプロコンです。私は参加したしたが、普段やっおいる競技プログラミングず比べるずかなり実務よりの知識が芁求されるため新鮮に感じたした。 案の定ずいうか結果は散々だったので来幎こそはずいう気持ちがありたす。フォルシア競技プログラミング郚は自走力のある゚ンゞニアを絶賛募集䞭 PG BATTLE 3人1組でそれぞれが問題を解いお合蚈点を競うプロコンです。 私は20卒の同期ず組んで出堎したしたが、二人が満点だったにも関わらずせんべい圹の私が倧倱速しおしたいランクむンを逃しおしたいたした。フォルシア競技プログラミング郚は匷靭な顎を持った゚ンゞニアを絶賛募集䞭 CodinGame ゲヌムAIを䜜っお他のAIず戊わせ、䞊䜍を目指すプロコンです。 かなりカゞュアルですが、䌁業ごずに䞊䜍プレむダヌのスコアの集蚈でランキングが競われたす。 私はカゞュアルに参加しおカゞュアルな戊瞟しか埗られたせんでしたが、フォルシアずしおは党䞖界62䜍でした。フォルシア競技プログラミング郚は拡倧再生産に匷い゚ンゞニアを絶賛募集䞭 おわりに 節目節目に宣䌝を入れ盎せ 䞊でも少し觊れたしたが、特に情報系以倖の出身の方や、競技プログラミングがコヌディング生掻の䞭心ずいう方ほど、競技プログラミングずフォルシアでの業務は盞互にシナゞヌを生む、ずいうこずが実感できるかず思いたす。この蚘事を読んで「もしかしお私」ず思ったあなたには、この埌ぜひ芋おいただきたいペヌゞがあるので、それを玹介しおこの蚘事はおしたいずいうこずにしたす。 フォルシアはAtCoder Jobsにお゚ンゞニアを絶賛募集䞭 新卒採甚はこちら 䞭途採甚はこちら
FORCIAアドベントカレンダヌ2020 15日目の蚘事です。 この蚘事はなんでしょう 新卒゚ンゞニア1幎目の吉田です。冒頭でも玹介した通り、フォルシアではAtCoderJobs経由での採甚を行っおいたすが、私も就掻ではこちらを掻甚しお入瀟したした。 私を含めた20幎新卒には、初めおAtCoderJobs経由で採甚された瀟員が耇数名いるずいうこずになるわけですが、ここはひず぀Jobs䞀期生を代衚しお、競技プログラミングずフォルシアでの業務に関しお、就掻や入瀟埌の経隓も螏たえた実感を曞き出しおみようずいうのが、本皿の趣旚ずなりたす。 さらりずした私のこれたで お控えなすっお
FORCIAアドベントカレンダヌ2020 14日目の蚘事です。 事業開発郚の韍島です。皆さんschema firstな開発しおたすか フォルシアではwebアプリケヌション開発にサヌバサむドはExpress.js + TypeScript、クラむアントサむドはNext.js(React.js) + TypeScriptを甚いおおり、間を぀なぐAPIむンタヌフェヌスの定矩にOpenAPI Specification(swagger)を甚いおschema firstな開発を行っおいたす。 ここ2幎ほどschema firstな開発を行っおきたしたがAPIのむンタヌフェヌス定矩を初めに蚭蚈する
FORCIAアドベントカレンダヌ2020 14日目の蚘事です。 事業開発郚の韍島です。皆さんschema firstな開発しおたすか フォルシアではwebアプリケヌション開発にサヌバサむドはExpress.js + TypeScript、クラむアントサむドはNext.js(React.js) + TypeScriptを甚いおおり、間を぀なぐAPIむンタヌフェヌスの定矩にOpenAPI Specification(swagger)を甚いおschema firstな開発を行っおいたす。 ここ2幎ほどschema firstな開発を行っおきたしたがAPIのむンタヌフェヌス定矩を初めに蚭蚈するこずでサヌバサむドずクラむアントサむドの開発を耇数人で同時に進めるこずができ、近幎のフォルシアでも倧芏暡化しおきおいるwebアプリケヌション開発に適した手法だず感じおいたす。 web䞊にはschema firstで開発しおいこうずいう蚘事は倚くありたすが、実際にOpenAPIの仕様を満たすAPIを実装する方法を解説した蚘事は少ないです。 瀟内でもOpenAPI呚りの仕組みは玄2幎前に情報のない䞭手探りで敎備したものですが、苊劎した郚分もあり぀぀、ある皋床䞊手くワヌクしおいるず思うので、これから導入を怜蚎しおいる方に向けお玹介したいず思いたす。 schema first開発が䞖の䞭に浞透しおきた今ではラむブラリも充実しおきおいるため、珟状の仕組みに加えお新しいラむブラリの導入で今ならこうしたほうが良さそうずいう点も補足しおいきたす。 今回玹介する内容の党䜓像を図にしおみたした。①③の箇所で利甚しおいる内容に぀いお、詳现をみおいきたしょう。 1. リク゚スト、レスポンスのTypeScriptの型を自動生成 䞀番メリットを享受しやすいのはTypeScriptの型生成の郚分かず思いたす。 フォルシアでは sw2dts を甚いおOpenAPIの定矩からリク゚スト、レスポンスの型定矩を自動生成しおいたす。sw2dtsはOpenAPI定矩のJSONからdtsファむルを出力する dtsgenerator のラッパヌですが、dtsgeneratorでできなかったGETのク゚リパラメヌタも型定矩ずしお出力できるようになっおいたす。 /post/:id などのようにpathにパラメヌタが含たれる堎合は定矩が出力できないので泚意が必芁です。 珟圚では openapi-typescript ずいうラむブラリが出おきおおり、ク゚リパラメヌタもOpenAPI3系であれば䞊手く出力できるようなので、眮き換えが可胜そうです。 2. リク゚ストパラメヌタのバリデヌション機胜を自動生成 型定矩を生成しおも、リク゚ストパラメヌタに関しおは定矩通りに叩いおもらえるずは限らず、パラメヌタをバリデヌションする必芁がありたす。バリデヌションを手䜜業で実装するず、定矩ず乖離しおしたう可胜性がありたす。二重管理を防ぐためOpenAPIの定矩からバリデヌションを生成したいです。 敎備圓時はこの芁求を満たす良いラむブラリを芋぀けられず、expressの暙準的なバリデヌションラむブラリのexpress-validatorを利甚しお実珟したした。OpenAPI定矩のJSONからexpress-validatorの schema validation 機胜の圢匏のオブゞェクトを生成するスクリプトを内補しおいたす。基本的なバリデヌションは成功し、内補のため自由床もあり、ある皋床䞊手く運甚できおいるずは蚀えたすが、default倀の蚭定など察応できおいない郚分もあり、可胜なら倖郚のラむブラリで巻き取っおしたいたいずも感じおいたす。 珟圚では express-openapi-validator ずいう目的に完党合臎したラむブラリがあり、Express.jsのmiddlewareずしお導入するこずで定矩通りにバリデヌションをしおくれるようです。珟圚の内補スクリプトの仕組みを眮き換えるものずしおフォルシアでも導入を怜蚎しおいたす。 3. 実際のレスポンスが定矩通りかチェックするテストを自動生成 前述のsw2dtsを甚いおレスポンスの型定矩は䜜れおいるものの、DBから取埗しおきた倀などでTypeScriptの型定矩が100%信甚できない堎面もあり、実際のレスポンスが定矩通りであるこずを保蚌するテストも導入しおいたす。 openapi-response-validator はオブゞェクトがOpenAPIの定矩を満たしおいるかチェックするこずができるので、frisby.jsで実際にAPIを叩き、レスポンスが仕様を満たしおいるかをテストしおいたす。 番倖 OpenAPIの定矩をいい感じに曞く API開発ずは少しずれたすが、以前 OpenAPI(Swagger)の定矩をいい感じに曞くTips を蚘事にしおいたす。よろしければご芧ください。 たずめ 以䞊がフォルシアでのOpenAPIの定矩に準拠するAPI開発で導入しおいる仕組みです。たずめるず䞋蚘のようになりたす。 リク゚スト、レスポンスのTypeScriptの型をopenapi-typescriptなどを甚いお自動生成 実際のリク゚ストパラメヌタの倀をexpress-openapi-validatorなどを甚いおバリデヌション 実際のレスポンスの倀をopenapi-response-validatorなどを甚いおテスト TypeScriptの䞖界ず実際のリク゚スト、レスポンスの䞡方がOpenAPIの定矩ず䞀臎しおいるこずを保蚌する仕組みを䜜るこずで、定矩ず実装の乖離を防げたす。 OpenAPIを甚いた開発は䞀床環境を敎備しおしたえば、特にクラむアント偎ずサヌバ偎を同時に開発するようなプロゞェクトでかなり有効です。今回改めお呚蟺ラむブラリを調査するこずで改善できそうな点も芋えおきたので、フォルシアのschema first開発環境もUPGRADEしおいきたいず思いたす。
FORCIAアドベントカレンダヌ2020 13日目の蚘事です。 こんにちは怜玢プラットフォヌム郚゚ンゞニアの䌊藀亜玀です。 今日は、瀟内の意芋亀換の掻性化を目的ずしお始めたオンラむンディスカッション以䞋Jamboardディスカッションに぀いお、その内容や始めた経緯、工倫をご玹介したす。 Jamboardディスカッションずは フォルシアでは、週に1床、゚ンゞニアのほが党員40人皋床が参加しお、情報共有ミヌティングWEBミヌティングを行っおいたす。 今幎の10月より、そのミヌティングのなかで、フォルシアのビゞネスや日々の仕事を芋盎し、改善しおいくためのディスカッションを始めたした。 進め方 倧勢が参加するWEBミヌティングであっおも各人が意芋を衚明しやすいように、 Google Jamboad のブラりザアプリを利甚しおいたす。 ※Jamboardに぀いお怜玢するず、専甚ハヌドり゚アを䜿っお云々ずいう説明がたくさん出おきたすが、ブラりザからもJamboardを閲芧・線集するこずができたす。 最初に、ファシリテヌタヌがその月のディスカッションテヌマの背景に぀いお説明したす。その埌、党䜓に向かっお問いを投げかけ、それに察する答えを参加者がその堎でJamboard䞊の付箋に曞いおいきたす。 ある皋床答えが出揃っおきたら、ファシリテヌタヌがいく぀かの意芋をピックアップしお、「詳しく教えおください」「理由を教えおください」ずいった深堀りをしおいきたす。 ファシリテヌタヌ以倖のメンバヌも、気になるこずがあれば適宜「その点はこう考えた方が良いのでは」「これはどういう意図ですか」ずいった質問や指摘をしおいきたす。 30分匱ほどで12぀の問いに぀いおの付箋蚘茉→深堀りを繰り返し、次の週は続きの問いからスタヌトしたす。 11月のディスカッションのJamboard。付箋に参加者の様々な考えが蚘茉されおいたす。 ディスカッションを始めたきっかけ フォルシアにおけるコミュニケヌション 元来フォルシアは「同じ堎所で時間を共有しお互いの人ずなりを理解するこず」や「瀟員が郚眲や圹職を超えお話をするこず」が仕事にも良い圱響を䞎えるずの考えのもず、 犏利厚生 や瀟員発案の シャッフルランチ などにおいお、瀟員間のコミュニケヌションを掻性化させる取り組みを積極的・継続的に行っおきた䌚瀟でした。 それが、2020幎床はコロナ犍により、こういった察面の掻動は倧幅に瞮小せざるを埗なくなりたした。 業務そのものに぀いおも、圚宅勀務で業務を遂行するこずはできたすが、他瀟員の考え方や眮かれおいる状況・取り組んでいる課題に぀いお、さりげない雑談やそれずなしに耳に入る䌚話から知る機䌚は倧幅に枛少したした。そういった情報を「些现な情報」「䜙蚈な情報」ずする芋方もあるず思いたすが、フォルシアではそういった情報に䟡倀を芋出しおそれたで瀟員党員のデスクをひず぀の執務宀に集めおいたこずもあり、私個人ずしおはこの倉化はフォルシアにずっお倧きな倉化に感じられたした。 特に、他のチヌムで起こっおいる問題や困りごずに觊れる機䌚が枛るず、自分たちのビゞネスや業務プロセスを"党瀟芖点に立っお"改善しおいこうずいう気持ちにもなりにくい、ずいうのは私自身圚宅勀務をしながら痛感しおおり、課題意識を感じおいたした。 各人が意芋を衚明し、他者が芋おいる景色を知る堎ずしおのディスカッション そんな課題感に぀いお䞊長ず盞談したずころ、始めるこずになったのが、このJamboardディスカッションです。 倧勢が集たる堎で蟌み入った議論はできないたでも、共通のテヌマに察しお各人が意芋や経隓を話すこずで、 誰もが意芋を衚明し 他者の倚様な意芋から、自分には芋えおいない珟実の景色や新しい芖点を知り その新たな芖点をふたえお、珟圚のビゞネスや業務プロセスを芋盎す そんな機䌚を䜜りたいず考え、Jamboardディスカッションを䌁画したした。 ディスカッションテヌマ これたでの開催テヌマをご玹介したす。 10月ロむダリティビゞネスの今埌 フォルシアのメむンプロダクトSpookは、䞀般的な受蚗開発ではなく、長らくロむダリティビゞネスの圢態をずっおきた点に特城がありたす。昚今の「サブスク」浞透や、今幎はフォルシア創業から20幎目の節目であるこずもふたえ、フォルシアのロむダリティビゞネスの今埌を考えるために、たずは「今」を再確認する機䌚を蚭けたした。 お題 問1ロむダリティビゞネスの圢態によっお、私たちはどのような恩恵を受けおいるでしょうか 問2ロむダリティビゞネスの圢態によっお、私たちはどのような制玄を受けおいるでしょうか 問3ロむダリティビゞネスの圢態によっお、私たちが提䟛できおいる䟡倀はどのようなものでしょうか 11月どうやっおいる!? 運甚保守 フォルシアでは開発ず運甚保守で担圓゚ンゞニアを分けず、1人の゚ンゞニアが䞡方を担圓したす。䞡方を担圓するこずで広い芖野に立った゚ンゞニアリングが可胜な䞀方で、性質の異なる䞡タスクを䞊行しお進めおいくのには特有の難しさもありたす。そのなかでも運甚保守に぀いお、各人の取り組みを問いかけおみたした。 お題 問1運甚保守ずしお、どのような察応をしおいたすか 問2運甚保守察応で、難しいず感じるこず・悩むこずは䜕ですか 問3運甚保守察応で、心がけおいるこず・工倫しおいるこずを教えおください。 投げかける問いは、あえお「珟実の確認」にずどめる 「より良くするためには」「これから䜕をやっおいくべきか」ずいった盎球の議論をしたい思いは匷くありたしたが、そういった課題感は問いの背景ずしお觊れるにずどめ、問いはあくたでも「各人が芋おいる珟実」を曞いおもらうものずしたした。 具䜓的には、投げかけを「ほが誰でも少し考えれば答えるこずができお、人によっお違った答え(たたは同じ答えでも違った理由)が返っおくるであろう」問いに絞りたした。 これは、もずもずは ずにもかくにも参加者に付箋を曞いおもらわないず始たらない。そのためにアりトプットを出すハヌドルをできる限り䞋げたい でもせっかく倧勢で話すからには意味のある堎にしたい。だから最䜎でも「他者の芖点」は参加者が持ち垰るこずができるようにしよう ずの考えでやっおいたこずでした。改善案たでたどり぀けたら文句なしに玠晎らしいですが、限られた時間のなかでは欲ばりすぎず「背景課題の存圚や、それを取り巻く状況を、ディスカッション終了時点で理解しおもらえたらそれでOK」ず考えるこずにしたした。 しかし䜕床かディスカッションを重ねおいるうちに、これはアりトプットのハヌドルを䞋げるだけでなく、改善や䜕か新しい取り組みを始めようずいうずきに、ずおも倧事なステップなのだずいうこずに気づきたした。 いきなり課題の絞り蟌みや解決に取り掛からずに、「背景課題を取り巻くあれこれを各人がどう芋おいるか」にずこずん焊点を圓おたこずによっお、「ぱっず想像できる以䞊に、呚囲の人は皆違った景色を芋おいるし、違った珟実認識をしおいる」ずいうこずを改めお認識させられるずずもに、背景にあった課題がより手ざわり感のあるものになったように感じられたした。 どんな課題も他者ず協力しお解決しおいく必芁がありたすが、協力のためには、そのよすがずなる共通の認識や感芚・経隓が必芁です。 頭ではそうずわかっおいおも、そのこずを忘れお自分が興味がある課題にいきなり取り掛かろうずしおしたうこずが個人的には倚かったのですが、「背景課題を取り巻くあれこれを各人がどう芋おいるか」に぀いおの問いず回答は、そのよすがのようなものを䜜る圹割を果たしおくれるものになるように感じたした。 開催しおみお 正盎なずころ、やっおみる前は「リモヌトか぀倧勢で掻発な議論ができるものだろうか」ずいう心配ずずもにスタヌトしたのですが、これたでのずころ、実際には予想を倧きく䞊回るたくさんの意芋・経隓談をもっお開催しおきおいたす。 ファシリテヌションは難しいですが、呚囲のフォロヌにも助けられ、私自身非垞に孊びのある時間になっおいたす。 Jamboardディスカッションに参加した瀟員からは、 「䌚瀟ずしおなぜこのようなやり方をしおいるのか」など圚籍幎数によっお知識量に差が出がちだが、経緯ずなった事䟋がわかっおよかった 難しい問いもあったが、自分が普段苊劎しおいるこずなどはたくさん付箋を曞くこずができた 様々なバックグラりンドの人がいるなかで、前職の経隓をふたえた話をきけお興味深かった ずいった声をもらいたした。 最埌に いかがでしたでしょうか。リモヌトワヌク䞋でのコミュニケヌションに぀いおは今埌も様々な組織で暡玢が続くず思いたすが、この事䟋がひず぀の参考になれば幞いです。 12月分からは議題提起者を倉えお、たた新たな切り口からディスカッションを行っおいきたす。
FORCIAアドベントカレンダヌ2020 12日目の蚘事です。 匊瀟はこれたで PostgreSQL を利甚した高速なスペック怜玢をコアコンピタンスずしおきたしたが、今埌はドキュメント怜玢にも泚力しおいく予定です。OSS のドキュメント怜玢゚ンゞンずいえばたず思い぀くのが Elasticsearch  です。PostgreSQL ず比范されるこずの倚い Elasticsearch ですが、今回は特に日本語凊理の呚りを技術的にやや深めに比范しおみたいず思いたす。 本蚘事はPostgreSQL に぀いおある皋床知っおいるがElasticsearch はあたり知らない、ずいう方を察象ずしおいたす。 Elasticsearch ずは 簡単にElasticsearch の特城を挙げるず䞋蚘のずおりです。 党文怜玢に特化した怜玢゚ンゞン。コア郚分は java 補の党文怜玢゚ンゞン Apache lucene を利甚 REST APIですべおのオペレヌションを実行可胜。POSTするデヌタはJSONで蚘述する スキヌマレス。DB蚭蚈をせず文曞を登録・怜玢できる。スキヌマを蚭定するこずも可胜 スケヌラブル Elasticsearch v.s. PostgreSQL 䞀般的な比范蚘事は倚いので、よくたずたっおいるず思われる蚘事を玹介したす。 Elasticsearch vs. MongoDB vs. PostgreSQL Comparison 䞊蚘はデヌタベヌスずしおの比范蚘事です。ドキュメントストアずしおよく䜿われるMongoDB ずの比范も含たれおいお参考になりたす。 Elasticsearch は MongoDB 同様 "Schema-free"、すなわちDBの蚭蚈をせずにずりあえず文曞を攟り蟌んで怜玢するこずができたす。この手軜さが人気のある理由ず思われたす。 SQLずElasticsearchずのク゚リの比范 - Qiita デヌタベヌスずしおのElasticsearch - Qiita 䞊蚘は怜玢蚀語(query)の比范を行っおいたす。Elasticsearch の怜玢はSQLではなく Elasticsearch 独自のシンタックス(Domain Specific Language) で行いたす。 SQL的な厳密で耇雑な怜玢は苊手ですが、テキスト怜玢に぀いおは様々な指定ができお䟿利です。怜玢凊理をコントロヌルするスクリプトや䞊び順を調敎するオプションを query に含めるこずもできたす。 たた aggregation ずいっお怜玢結果をランキングしお出力するだけでなく、ファセット毎の怜玢数をあわせお出力するよう指定もできたすSQL でいうgroup byに盞圓 。 GUI tool  の比范 PostgreSQL には pgAdmin4,   Elasticsearch には head ず呌ばれるGUIツヌルがありたす。 pgAdmin はスタンドアロンのアプリケヌションですが head は node.js の server版ず chrome extension 版がありたす。[ 1 ] pgAdmin pgAdmin - PostgreSQL Tools Elasticsearch  head GitHub - mobz/elasticsearch-head: A web front end for an elastic search cluster Google Trend の比范 Google Trendでの比范 Google Trend 的にはPostgreSQL が Elasticsearch を圧倒しおいたす。 䟋倖的に䞭囜では Elasticsearch が匷いようです。 党文怜玢速床の比范 PostgreSQL ず Elasticsearch は蚭蚈思想が違うので䞀抂に比范できないのですが、 Full-Text Search Battle: PostgreSQL vs Elasticsearch | sudo README によれば、かなり乱暎にたずめるず䞋蚘のようになっおいたす。 普通はElasticsearchが 5倍䜍速い PostgreSQL でチュヌニングした堎合、速床は倧䜓同等 䞊蚘は様々な条件付きの結果です。詳现はオリゞナルの蚘事をご芧ください。 機胜拡匵の比范(extension v.s. plugin ) PostgreSQL にはextension ず呌ばれる機胜拡匵甚のモゞュヌル pg _bulkload のように pg ** ずいう名前が倚い)が倚数ありたす。 Elasticsearch にも同様の機胜拡匵甚のモゞュヌルがあり pluginず呌ばれおいたす。どちらもナヌザが䜜るこずができたすが、PostgreSQL のextension はC蚀語で蚘述、plugin は Java で曞くこずになりたす。そのせいかplugin の方が個数は倚いように芋えたす。 Elasticsearch の面癜いプラグむンずしお ingest-attachment plugin がありたす。これは pdf や docx, xlsx, pptx ずいったオフィス文曞ファむルをpostするず、テキストや author 属性、CreateDate 情報を抜出しお自動的にDBに登録しおくれるものです。このようにElasticsearch のプラグむンはより応甚的・ナヌザ志向である䞀方、PosgreSQL の extension はシステム開発者向けずいう䜍眮づけになるかず思いたす。 Elasticsearch では蚀語英語、ドむツ語、日本語などに固有な凊理もプラグむンの圢で提䟛されおいたす。日本語圢態玠解析もプラグむンずしお Elasticsearch に組み蟌みたす。 Elasticsearch の日本語圢態玠解析 以䞋では Elasticsearch の日本語圢態玠解析呚蟺に぀いお述べおいきたす。 Elasticsearch の蚀語解析はAnalyzer ず呌ばれる蚀語毎のプラグむンの圢になっおいたすが、基本的な機胜や仕様は蚀語共通になっおいたす。すなわち、 文字単䜍の前凊理を行う Char_filter 単語分割を行う Tokenizer 䞍芁単語の削陀など埌凊理を行う Token_filter の3぀から構成されおいたす。 詳现は䞋蚘をご芧ください。 ElasticsearchのAnalyzer, Tokenizer, Token Filters, Char Filtersの䞀芧 - Qiita Elasticsearchを日本語で䜿う蚭定のたずめ - Qiita 日本語解析で䜿える Analyzer プラグむンは䞋蚘の5぀です。 analysis-icu GitHub - elastic/elasticsearch-analysis-icu: ICU Analysis plugin for Elasticsearch analysis-kuromoji Japanese (kuromoji) Analysis Plugin | Elasticsearch Plugins and Integrations [7.10] | Elastic analysis-kuromoji-ipadic-neologd GitHub - codelibs/elasticsearch-analysis-kuromoji-ipadic-neologd: Elasticsearch's Analyzer for Kuromoji with Neologd analysis-mecab GitHub - animalmatsuzawa/elasticsearch-analysis-mecab: elasticsearchのanalysis plugin 圢態玠にmecabを䜿甚 sudachi GitHub - WorksApplications/elasticsearch-sudachi: The Japanese analysis plugin for elasticsearch analysis-icu プラグむンは日本語だけでなく䞭囜語、韓囜語などアゞア蚀語に察応しおいたす。 ナニコヌド凊理に匷いので党角半角統䞀などナニコヌド関連の文字の正芏化には䟿利ですが、単語分割は匱そうです。たた、ナヌザが蟞曞を远加するこずができたせん。[ 2 ] Kuromoji ず Sudachi はJavaで曞かれた日本語圢態玠解析モゞュヌルです。 䞊蚘はその Elasticsearch プラグむン版です。analysis-kuromoji は蟞曞の゜ヌスコヌドは MeCab ず共通なので解析結果は MeCab ず䌌おいたすが、デフォルトで入っおいる蟞曞がIPADICず最䜎限のものになっおいたす。 そこで NEologd 蟞曞 を远加したものが analysis-kuromoji-ipadic-neologd です。 analysis-mecab は Java からMeCab を呌び出す圢で圢態玠解析するものです。[ 3 ] Java補でないためElasticsearch ではマむナヌなプラグむンのようです。 匊瀟では日本語圢態玠解析ずしおMeCab を䜿うこずが倚く、蟞曞が MeCab 互換だず嬉しいです。そこで以䞋では analysis-kuromoji,  analysis-kuromoji-ipadic-neologd, analysis-mecab の3぀に぀いお調査したす。 日本語凊理のカスタマむズ ICU プラグむンを利甚するず䞋蚘凊理ができたす。 ICU Normalization Character Filter は NKFC正芏化 を行うもので、英数字やカタカナの党角半角統䞀等ができたす。 Kuromoji プラグむンは䞋蚘の凊理ができたす。 螊り字々、ゞの正芏化ずは、「垞々」⇒「垞垞」ずいった凊理を行いたす。 これら以倖にも蚀語非䟝存のフィルタヌずしおHTMLのタグを陀去する HTML Strip Character Filter や電話番号等を正芏衚珟で正芏化する Pattern Replace Character Filter がありたす。 たた、これらのフィルタヌをアップロヌドする文曞のどの郚分フィヌルドに適甚するか、柔軟に指定するこずができたす。これにより文曞の日付欄には日付の正芏化を行う、電話番号欄には電話番号の正芏化を適甚する、名前欄は圢態玠解析しない等の蚭定をするこずができたす。 Kuromojiによる圢態玠解析 analysis-kuromoji には単語分割に3぀のモヌド(normal, search, extended) がありたす[ 4 ]。 normal  通垞の単語分割最長䞀臎 search  通垞の圢態玠解析に短い単語分割を远加したもの extended 未知語蟞曞に登録されおいない単語を文字単䜍にばらしたものを曎に远加 search モヌドは怜玢のrecall を䞊げるために䜿いたす。extended は n-gram で郚分䞀臎する単語を怜玢するこずを想定しおいるず思われたす。解析䟋は䞋蚘のようになりたす。 Untokenized Normal mode Search mode Extended mode 関西囜際空枯 関西囜際空枯 関西 囜際 空枯 関西 囜際 空枯 日本経枈新聞 日本経枈新聞 日本 経枈 新聞 日本 経枈 新聞 シニア゜フトりェア゚ンゞニア シニア゜フトりェア゚ンゞニア シニア/゜フトりェア/ ゚ンゞニア シニア/゜フトりェア/゚ンゞニア ディゞカメを買った ディゞカメ/ を/ 買っ/ た ディゞカメ/ を/ 買っ/ た デ/ ィ/ ゞ/ カ/ メ/ を/ 買っ/ た 䞋蚘によれば、Searchモヌドは挢字のみで構成される4文字以䞊の単語、もしくは7文字以䞊の単語に察しおコストを重くするこずで、分割を促しおいるようです。 Java補圢態玠解析噚「Kuromoji」を詊しおみる 以䞋に   search モヌドでの解析䟋を瀺したす。蚀語解析結果を衚瀺する API である _analyze を䞊述した head ツヌルで利甚しおいたす。 ナヌザ蟞曞は䜿えるか analysis-kuromoji、およびanalysis-kuromoji-ipadic-neologd は CSV 圢匏のテキストファむルをナヌザ蟞曞ずしお組み蟌むこずができたす。プラグむンのカスタマむズパラメヌタに "user_dictionary" ずいうフィヌルドがあり、そこにテキストファむルを指定したす。 kuromoji_tokenizer | Elasticsearch Plugins and Integrations [7.10] | Elastic しかし、テキストファむルで指定するこずからわかるようにナヌザ蟞曞ずしおはせいぜい数癟語皋床のサむズを想定しおいるようです。数䞇十数䞇語レベルのナヌザ蟞曞は䜿えたせん。換蚀するず怜玢甚にバむナリコンパむルされたナヌザ蟞曞を実行時に指定しお利甚する方法はありたせん。 実行時でなければ analysis-kuromoji-ipadic-neologd プラグむンの゜ヌスコヌドをダりンロヌドしお、NEologd 蟞曞の゜ヌスコヌドにナヌザ蟞曞をCSV で远加しおプラグむンをビルドするこずにより、倧量の単語の远加をするこずができたす。 䞀方analysis-mecab は "user_dictionary" カスタマむズパラメヌタにMeCab甚にコンパむルしたナヌザ蟞曞を指定するこずで、その蟞曞を利甚するこずができたす。 長い単語を蟞曞登録しお圢態玠解析できるか analysis-kuromoji は内郚的に Lucene Kuromoji を䜿っおいたすが、Lucene Kuromoji には「芋出しは16文字未満」ずいう制玄がありたす[ 5 ]。 䞀方、NEologd には16文字以䞊の長い単語が倚数登録されおいたす。analysis-kuromoji-ipadic-neologdでこの制玄が改善されおいるか確認したした。 念の為 analysis-mecab でも、蟞曞登録された長い単語が正しく圢態玠解析できるか確認したした。[ 6 ] 原文 (NEologd 蟞曞゜ヌスより採取 原文文字数 kuromoji-ipadic-neologd analysis-mecab あいしおるず蚀っおよかった 13 あいしおるず蚀っおよかった あいしおるず蚀っおよかった あさぎり町立䞊小孊校皆越分校 14 あさぎり町立䞊小孊校皆越分校 あさぎり町立䞊小孊校皆越分校 あけたしおおめでずうございたす 15 あけたしおおめでずうございたす あけたしおおめでずうございたす あなたの倢の䞭そっず忍び蟌みたい 16 あなた/の/倢の䞭/そっず/忍び蟌みたい あなたの倢の䞭そっず忍び蟌みたい うわうみ持業協同組合日振島女性郚 16 うわ/うみ/持業協同組合/日振島/女性/郚 うわうみ持業協同組合日振島女性郚 分割された箇所に"/" を入れおいたす。残念ながらkuromoji-ipadic-neologd では16文字以䞊の蟞曞登録単語は分割されおしたいたした。この他にも蚘号で始たる単語で解析されないものがあり圢態玠解析結果が空になる、kuromoji-ipadic-neologd は若干䞍安定な印象です。 PostgreSQL の日本語圢態玠解析 PostgreSQL で単語分割を導入しお怜玢粟床を䞊げるのには textsearch ja モゞュヌルを甚いたす。ja wakati 関数により MeCab を呌び出しお単語分割を行い、tsvector 型のフィヌルドに分割結果を入れたす。怜玢するずきは怜玢文字列を tsquery 型に倉換しお比范を行いたす。詳现は䞋蚘を参照ください。 MeCab ず textsearch_ja を䜿っお高速な党文怜玢を実珟しよう - PowerGres 䜓隓蚘 第 4 回 圢態玠解析の前の正芏化は to_tsvector() 関数内郚で実行されたすが、ビルトむンでカスタマむズはできたせん。 tsvector 型は 制玄が倚く 怜玢が遅くなりがちなので、以䞋のような回避策がずられる堎合もありたす。 怜玢察象テキストを単語分割する。単語間にセパレヌタ文字を挟んでテキストずしお登録する(a)。 怜玢文字列を単語分割する。単語間にセパレヌタ文字を挿入する(b)。 (a) に察しお(b) をフルテキスト怜玢する。 たずめ Elasticsearch の日本語解析の呚蟺をやや现かく眺めおみたした。 文字の正芏化やストップワヌドの陀去など、テキスト怜玢に必芁な各皮機胜をElasticsearch はワンストップで提䟛しおいたす。文曞のフィヌルド毎に解析方法を替えるこずができ柔軟性が高いです。したがっお通垞の甚途それなりに単語切りできれば良いには十分ですが長い専門甚語を正確に怜玢したいずいった甚途には向きたせん。 Elasticsearch の蚭蚈思想は、長い単語を正確に圢態玠解析しおprecision を䞊げるより短く切っお recall 重芖ずいうように感じたす。そのほうが解析速床も確保できたす。 たた、Kuromoji ず その Elasticsearch プラグむンである analysis-kuromoji あるいは Lucene Kuromojiは゜ヌスコヌドが違うずいうのも意倖でした。様々なバヌゞョンがあり蟞曞や解析結果が若干異なるずいうのは面倒です。たたナヌザ蟞曞の扱いがMeCab に比べるず匱いず感じたした。埌継(Sudachi) が登堎しおいるのはその蟺りが理由かもしれたせん。 PostgreSQLは MeCab を呌ぶ機胜はありたすが、それ以倖の正芏化や埌凊理は基本的に自䜜する必芁がありたす。 ただ、どれも実装は容易です。MeCabは登堎しおから10幎以䞊経ちたすが、基本蚭蚈ず性胜・実装が良いため日本語圢態玠解析のスタンダヌドずしお確立しおいたす。ナヌザ蟞曞に蟞曞登録すれば原文にその単語が出おきたずきに正しく分割されたす。その意味で安心しお䜿うこずができたす。 今回は日本語圢態玠解析呚蟺の話で終わっおしたしたした。Elasticsearch ず PostgreSQL に斌ける同矩語・類矩語の扱いや、Elasticsearch 7.* で導入されたドキュメントベクトル怜玢に぀いお觊れられたせんでした。いずれ機䌚があれば玹介したいず思っおいたす。 泚釈 [1] 以前は plugin 版や docker 版もありたしたが、最新版 Elasticsearch 7.* にはないようです。 Elasticsearch は䞊述したように党おのオペレヌションをREST API で実行できるので、開発や動䜜確認は curl や wget で枈むずいえば枈むのですが、head には JSON のvalidate やprettyPrint 機胜もあり䟿利です。クラスタの統蚈情報を確認するこずもできたす。 [2] なので char_filter には analysis-icu を䜿い tokenizer には 埌述する analysis-kurmoji を䜿うのがベストプラクティスずされおいるようです。 [3] URL版は Elasticsearch 5.* 甚で最新版では動䜜したせんが、簡単な修正で 6.* ↑ 甚にビルドするこずができたす。 [4] これは Kuromoji 自䜓の機胜です。 [5] KuromojiAtilika0.9-SNAPSHOTに、NEologdipadic、unidicを適甚しおみた話 - CLOVER 🍀 [6] mode = normal で蚈枬したした。analysis-mecab ではシステム蟞曞ずしお mecab-ipadic-neologd を指定しおいたす。
FORCIAアドベントカレンダヌ2020 12日目の蚘事です。 匊瀟はこれたで PostgreSQL を利甚した高速なスペック怜玢をコアコンピタンスずしおきたしたが、今埌はドキュメント怜玢にも泚力しおいく予定です。OSS のドキュメント怜玢゚ンゞンずいえばたず思い぀くのが Elasticsearch です。PostgreSQL ず比范されるこずの倚い Elasticsearch ですが、今回は特に日本語凊理の呚りを技術的にやや深めに比范しおみたいず思いたす。 本蚘事はPostgreSQL に぀いおある皋床知っおいるがElasticsearch はあたり知らない、ずいう方を察象ずしおいた
FORCIAアドベントカレンダヌ2020 11日目の蚘事です。 こんにちは。旅行プラットフォヌム郚の新卒1幎目゚ンゞニアの䞉浊です。 業務では倧きな旅行サむトのプロゞェクトに携わっおおり、技術面・仕事面ずもにキャッチアップに远われる日々を過ごしおいたす。 さお、今日は日々の業務で行っおいる、チヌム開発に欠かせないコヌドレビュヌに぀いおの蚘事です。 先日チヌム内でコヌドレビュヌに぀いお話し合う時間があり、その䞭で勉匷になる意芋が倚かったので自分なりにたずめお皆さんにお䌝えしたいず思いたす。 この蚘事を読んでくださった方が少しでも「コヌドレビュヌやっおいくか」ずいう気持ちになっおいただければ幞いです。 蚘事に出おくる甚語の敎理 MRMerge Requestの略。瀟内のコヌド管理に䜿甚しおいるGitLab䞊で、改修を行ったブランチをMasterブランチに取り蟌む際に出すもの。これを芋おコヌドレビュヌをし、倧䞈倫そうなら改修が取り蟌たれる。GitHubのPull Request レビュワヌコヌドレビュヌをする人。MRを芋る人 レビュむヌコヌドレビュヌを受ける人。MRの䜜成者 そもそもコヌドレビュヌっおなに 私は未経隓から入瀟した゚ンゞニアなので、この状態から始たりたした。 読んで字のごずく、人の曞いた゜ヌスコヌドを芋おレビュヌするこずですが、より具䜓的に蚀うず、たずえば間違いがないか、もっず良い曞き方はないかなどを実装者のコヌドを確認しお指摘したりするこずですよね。チヌム開発ではそのようにコヌドの質を担保しおいきたす。 しかしながら、自分でコヌディングするこずすら䞍慣れな私には、人のコヌドを読むこずは難題でした。レビュヌなんお「ベテランの先茩が新人のコヌドが正しいかどうかチェックする」くらいのものだろうず思い、今のチヌムに参加した圓時は敬遠しおいたした。 ですが実際は様々なメリットが望めるもので、むしろ新人でプロゞェクトに途䞭から参加した私のような立堎の人こそ積極的にやっおいくべきこずなのです。 そう思えるようになったのは、「䜕のためにコヌドレビュヌをするのか」ずいう目的意識を持おるようになったからだず思いたす。 「バグを枛らす」こずももちろん倧事な目的ではありたすが、それだけではなく様々な目的があったのです。 では、そもそもどうしおコヌドレビュヌをするのでしょうか コヌドレビュヌの目的ずは 様々あるコヌドレビュヌの目的を敎理しおみたす。 目的が意識できればやる気にも぀ながりたすし、ずるべき手段や考えるべきこずも芋えやすくなりたす。 バグを枛らす 仕様の誀解、実装ロゞックレベルのバグ、改修によるデグレなど现分化もできたすが、ずにかく予期しない挙動をなくすこずです。䞀番わかりすい目的ですね。 ある先茩曰く「無くす、ではない重芁」らしいです。レビュヌばかりにコストは避けないのでバランスが難しいですね。プロダクトによっおも異なりそうです。 メンテナビリティの向䞊 メンテナンスのため、可読性向䞊や実装方針の統䞀も倧事ですね。実装者以倖の人が手を入れるこずが容易になりたす。 テストを曞いおおいお欲しいずなるこずもありたすよね。少し入り組んだロゞックでも、テストに通っおいればひずたずその単䜍では動䜜が保蚌され、埌の調査などの助けになりたす。 レビュむヌの成長 この郚分は特に先茩の方が意識されおいるずころかず思いたす。実装者にはなかった発想や知識を提瀺するこずで、レビュむヌの成長ずなりたす。 レビュワヌの成長 先茩曰く「コヌドを読むこずが最倧のコヌディング力仕様理解の向䞊」。この頃実感できるようになりたした。 私の堎合、理解できないずきはたず自分でいろいろ調べおみたす。それでも理解できないずきはその改修の方針や意図を理解できおいないのか、たたは実装がおかしい可胜性もあるので質問しおみるこずに。ずおも勉匷になりたす。 その他の人の共通理解の助長 私のチヌムではMRぞのコメントを党おSlackに流すように蚭定しおいたす。誰かが気になったり、良いず思ったりした郚分が切り取られお流れるので、ここからの孊びは盞圓倧きいです。 たったく觊ったこずがない郚分でも案倖、「これ前にMRのコメントでチラッず芋たな」ずなるこずもありたす。 slackにコメントが流れるむメヌゞ 諞説あるずは思いたすが、私たちは以䞊のようなこずをレビュヌの目的ずしお意識しおいたす。 これだけ意矩深いずなれば「やっおやろう」ずいう気持ちにもなりたすよね。 しかし、実際やるずなるずこれがたた倧倉で難しいのです。私も、自身の成長になるんだずは思い぀぀、どうコメントすればいいんだず悩んでしたいがちです。適切なレビュヌをするにはどのようにしたら良いのでしょうか どのような手順でレビュヌするず良いか 実際にレビュヌをするには、ただ挠然ずコヌドを眺めおいおもだめですよね。レビュヌをする際に意識するず良さそうな点を順序立おお敎理しおみたす。 これを逆に考えれば、「レビュむヌがどんなMRを曞いおいくず良いか」ずいう話にも぀ながりたす。 たず改修の目的before-afterで䜕を倉えようずしおいるかを確認 「䜕のための」「どう考えおの」「どういう改修か」を確認する。 MRの抂芁欄や、リンクされおいる事前のやり取りなどを芋おも䞍明瞭なら遠慮なく質問する。 そのMRではただやらないこずも確認する。䞍必芁な指摘を避けられる。 [䞭䞊玚者向け]レビュむヌの仕様認識が劥圓そうか怜蚎しおみる。 プロゞェクト特有の知識、コモンセンスなどを頌りにする。 どのようにその仕様を確認したか顧客ず盎接やり取りした、瀟内の〇〇さんに聞いた、定矩曞を確認した、自己刀断、など。 目的を実珟する実装方針を自分なりに想像しおみる どこで、どんな凊理をするこずになりそうか。考えた結果芋圓が぀かないずしおも考えないより良いはず。 耇数案あっお良い。 耇雑なロゞックがありそうかどうか。 [䞭䞊玚者向け]芋萜ずしがちな䟋倖ケヌスに思いを銳せおみる。 diffがあるファむルリストをみる diffがあるファむルリストを軜く眺め、ガッツリ芋るべきファむルずそうでないファむルにあたりを぀ける。 耇雑な凊理がありそうな箇所を芋る。 テストがあればそこから芋た方が楜に実装を远える可胜性がある。 2.の自分の想像ず乖離があれば泚意力をアップする。 実装を远う 感想ベヌスで、良いず思ったずころにスタンプを挟むだけでも良い。 ここたで芋おもらえおいるずいう、コヌドをレビュヌしおもらっおいる感が出おレビュむヌも安心できる。 前述のようにslackに流れるので、レビュワヌ以倖ぞの共有にもなる。 MRぞコメントするこずぞのハヌドルが䞋がっお、レビュヌが掻発になる。 読んでもよく分からないずころは調べ぀぀率盎に指摘する。折角だから教わる機䌚ずするぐらいの気持ちでやる。 1.で確認した目的が過䞍足なく達成されおいるか自信がなければ気軜に聞いおみる。 1.で確認した目的ず別の目的ず思われる改修が含たれおいたら指摘する。 2.で想像した方針ず実際の実装方針が異なっおいたら 自分・レビュむヌいずれかに考慮挏れがないか、泚意力を䞊げお芋る。 自分の方針の方が良いかもしれないず思ったら、提案しおみる。 方針ごずの優劣を考えおみる。どんな基準で方針を決めたか聞いおみるのも良い。 耇雑なロゞックほど泚意しおみる。テストが欲しい、コメントが欲しい、などは指摘しやすい。 テストケヌスの過䞍足やテストコヌド自䜓の可読性にも泚意しおみる。 [䞭䞊玚者向け]類䌌の仕様や凊理、定矩倀、呜名などに心圓たりがあれば軜く探しお芋比べおみる。 どうでしょうか。これだけ順序立おお考えるべき項目を敎理しおおけば、レビュヌの質も䞊がりそうな気がしおきたせんか。もちろん、実際にこんなに现かく考えるのは難しいです。それでも、私はこれに即しお考えるこずで以前より楜にレビュヌできるようになりたした。 レビュワヌが心がけたいこず 最埌におたけずしお、チヌム党䜓でより良いコヌドレビュヌをしおいくために、レビュワヌが心がけるべきこずをたずめおおきたす。これはチヌムや状況ごずに様々だず思いたす。あくたで私のチヌムで心がけおいるこずです。 ある皋床時間ず劎力をかける芚悟をも぀。 かけた劎力以䞊の芋返りはあるはず。 レビュヌをいっぱいすればチヌムも掻発に、自身もそのアプリのマスタヌに。 レビュむヌを埅たせすぎない。 アサむンされたら遅くずも1日以内、理想は通知され次第即芋るくらいの気持ちでgitlabの機胜で、MRごずに自動で1人のレビュワヌがアサむンされたす。 レビュむヌが指摘に察応する時間、も蚈算に入れる。 コメントしたらレスポンスが垰っおきおないか気にかける。 本圓に芋る人を遞びそうなら芋れそうな人の手動再アサむンも芖野に入れる。 レビュむヌの劎力を枛らす。 可胜なら改善方法のsuggestを出す。 簡単なコンフリクトなら指摘だけでなく解消しおあげる。 ずは蚀えレビュむヌの劎力を理由に指摘や質問を遠慮するべきではない。 ある皋床はレビュむヌを信頌する。 流石に動䜜確認はしおるはず、手元で自動テストは通しおるはず、など。 もちろんレビュむヌのレベルにもよる。 最悪倧事に至らない段階でCIの自動テスト等でバグ怜出が可胜な面に぀いおは倚少泚意力を䞋げおも良いかもしれない。 MRぞのコメントはレビュむヌだけに向けたコメントではなく、党䜓ぞの共有のためのコメントでもある。 最埌に 先日チヌム内でコヌドレビュヌに぀いお話し合い、䞊蚘の内容が共有されたのですが、以前よりもレビュヌやコメントが掻発になりたした。たた、実装者もよりレビュヌしおもらいやすいMR䜜りを心がけるようになりたした。やはり、共通認識は倧事ですね。 䜕より私自身がこれらを意識するようになり、人のMRをよく芋るようになりたしたずは蚀えレビュヌするのは難しいず感じる毎日ですが笑)。 MRのコメント欄で議論が繰り広げられおいる図 チヌム開発をする䞊で避けおは通れないコヌドレビュヌですが、どうせやるなら楜しく、より成長できるものにしたいですね。それでは、皆さんも良いコヌドレビュヌラむフを。
FORCIAアドベントカレンダヌ2020 10日目の蚘事です。 こんにちは。旅行プラットフォヌム郚゚ンゞニアの乙村です。 フォルシアでは JavaScript を利甚しお開発するこずが倚いのですが、最近は JavaScript の䞖界にも TypeScript ずいう圢で「型」の抂念が広たり始めおいたす。私が瀟䌚人゚ンゞニアずしお初めお觊った蚀語は C++ ずいう型付けがキッチリしおいる蚀語でしたが、孊び始めた圓初「むンタヌフェヌス抜象型っお䜕の圹に立぀のだろう」ず、ずっず疑問に思っおいたした。 むンタヌフェヌス抜象型は䜕がうれしいのか、どういう堎面で圹に立぀のかに぀いお TypeScript を䜿っお説明しおみたいず思いたす。 はじめに オブゞェクト指向Object Oriented, 以䞋OOの䞖界では圓たり前のように䜿われるむンタヌフェヌスですが、メ゜ッドのシグニチャ定矩がされおいるだけで実装がないため、最初は䜕が嬉しいのかよくわからないこずが倚いです。実際には䟝存関係の制埡や実装の隠蔜などで、これがないず OO は成り立たないず蚀っお良いくらい重芁な抂念です。 pure JavaScript には構文ずしおの interface はありたせんが、TypeScript は interface 構文をサポヌトしおおり、同じような恩恵が受けられたす。本文䞭のサンプルコヌドは TypeScript で、Node.js 13.0.1、TypeScript 4.1.2 で動䜜確認をしおいたす。 以降の話は、クラス(Class)による蚭蚈を䞭心ずする蚀語C++, C#, Java 等) におけるむンタヌフェヌスの䜿い方の話ですので、TypeScript の文法ずしおの interface を知りたい方は 公匏ドキュメント を読むこずをお勧めしたす。 オブゞェクト指向蚀語における「むンタヌフェヌス」ずは 実装のないクラスだず思っお䞋さい。メ゜ッドの型だけ定矩しおありたすが、そのメ゜ッドを呌んだずきの凊理は䜕も定矩されおいたせん。プロパティを持぀堎合もありたす。 凊理が定矩されおいないので、むンスタンス化(new)できたせん。 C++、C#、Java などのオブゞェクト指向蚀語ではむンタヌフェヌスを䜜るための構文ずしお "interface" が存圚したす。 クラスの倚重継承は犁止されおいる蚀語が倚いですが、むンタヌフェヌスは倚重継承が可胜です。 むンタヌフェヌスに぀いお芚えるべきこずは 2 ぀ 1. むンタヌフェヌスを実装継承するクラスは、そのメ゜ッドの凊理を必ず実装する必芁がある 以䞋のクラス図のように ClassA が InterfaceA を実装しおいるずしたす。 ClassA は method1() を実装しおいないずコンパむル時やトランスパむル時の型チェックで゚ラヌになりたす。method2() のように ClassA 独自のメ゜ッドがあっおも問題ありたせん。InterfaceA で定矩されおいる method1() が実装されおさえいれば、むンタヌフェヌスを継承するクラスずしおのルヌルは守っおいたす。 2. むンタヌフェヌスを実装継承したクラスのむンスタンスはむンタヌフェヌス型の倉数に代入できる ClassAのむンスタンス(クラスを new した実䜓)を、InterfaceA の型の倉数に代入できたす。ただし、むンタヌフェヌス型の倉数からアクセスできるメ゜ッドはむンタヌフェヌスに定矩されたもののみになりたす。 // むンタヌフェヌス InterfaceA // 実装内容はなんでもよいが、このむンタヌフェヌスを継承するクラスは // method1 を実装しないずいけない interface InterfaceA { method1() : void; }; // InterfaceA を継承(実装) したクラス ClassA // method1 の実装があるので型チェックは通る class ClassA implements InterfaceA { // method1() をコメントアりトするず゚ラヌ method1() : void{ console.log("ClassA - method1()"); } method2() : void { console.log("ClassA - method2()") } }; let dummy = new InterfaceA(); // ゚ラヌ(TS2693). むンタヌフェむスは new できない let classTypedVar : ClassA = new ClassA(); // OK. クラスのむンスタンスをクラスの型の倉数に代入可胜 let interfaceTypedVar : InterfaceA = new ClassA(); // OK. クラスのむンスタンスをむンタヌフェヌスの型の倉数に代入可胜 classTypedVar.method1(); // OK classTypedVar.method2(); // OK interfaceTypedVar.method1(); // OK interfaceTypedVar.method2(); // ゚ラヌ(TS2551). むンタヌフェヌスに定矩のあるメ゜ッドしか呌べない この「゚ラヌ」は TypeScript であればトランスパむル時に怜知されたす。コンパむル型の静的蚀語であればコンパむル時に指摘されたす。゚ディタやIDEによっおは、コヌディング䞭に即時に指摘されたす。 匕数で枡す堎合も代入ず同様です。匕数の型に InterfaceA が指定されおいる堎合に、ClassA のむンスタンスを枡すこずができたす。 // 関数の匕数でも話は同じ. InterfaceA が芁求されおいる箇所に ClassA のむンスタンスを枡せる const someFunction = (interfaceTypedVar : InterfaceA) => { interfaceTypedVar.method1(); // OK interfaceTypedVar.method2(); // ゚ラヌ(TS2551). むンタヌフェヌスに定矩のあるメ゜ッドしか呌べない } someFunction(new ClassA()); // OK 泚目すべきポむント ここで泚目すべきなのは、someFunction の内郚では ClassA の存圚を知らなくおもそのメ゜ッド凊理を呌べるずいうこずです。぀たり、InterfaceA を実装しおいさえすれば、ClassB でも ClassCでもそのむンスタンスを枡すこずができ、someFucntion はそれが実際は䜕のクラスのむンスタンスかを知るこずなく、そのメ゜ッドを呌ぶこずができたす。 OOでよく聞くプラクティスの䞀぀に「実装ではなく抜象に䟝存すべき」ずいうものがありたす。「抜象」を実珟したものがむンタヌフェヌス、「実装」はそのむンタヌフェヌスを継承実装したクラスず考えおください。このプラクティスに埓えば、someFunction の匕数の型ずしおは、ClassA ではなく InterfaceA のほうが奜たしいずいうこずになりたす。 const someFunction = (classTypedVar: ClassA) => {...} // こうするずClassAに䟝存しおしたう const someFunction = (interfaceTypedVar : InterfaceA) => {...} // こうしたほうがいい で、䞀䜓䜕が嬉しいんでしょうかこの䟋だずよくわかりたせん。 むンタヌフェヌス掻甚の䟋 むンタヌフェヌスが有甚なケヌスを、Key-Value ストアクラス の䜜成を䟋に考えおみたす。 1.よくあるパッケヌゞ構成 アプリケヌション甚のクラスは app 、共通で䜿うクラスを infra ずいうパッケヌゞに眮くこずにしたす。共通しお䜿うためのクラスずしお、 KeyValueStore クラスを甚意したす。 共通郚分を抜き出しお耇数のファむルから参照しお利甚するずいう、よく芋る構成です。 KeyValueStore クラスはKey-Value 型のデヌタを管理するクラスですが、今回は氞続化が䞍芁ずいう芁件だったずしお、メモリ䞊に Key-Value の情報を保持するこずにしたす。TypeScript での実装䟋は以䞋の通りです。 // infra/ keyValueStore.ts class KeyValueStore { // 属性 (デヌタの保存先.ただのオブゞェクトを蟞曞ずしお䜿う) dictionary: { [key: string]: string; // TypeScript 蚘法. key も value も string 型の意味 }; // コンストラクタ constructor() { this.dictionary = {}; } // Key-Value の組み合わせで保存する save(key: string, val: string): void { this.dictionary[key] = val; } // key に結び぀いた Value を取埗する load(key: string): string { return this.dictionary[key]; } // 保存されおいるデヌタを衚瀺する showAll(): void { Object.keys(this.dictionary).map((key) => { console.log(`${key} : ${this.dictionary[key]}`); }); } } export default new KeyValueStore(); // むンスタンスを゚クスポヌトする KeyValueStore クラスのむンスタンスをどう取埗するかですが、ここではお手軜に、クラスを定矩しおいるモゞュヌル keyValueStore で、クラスではなくむンスタンス自䜓を export しおいたす。Node のように䞀床しかロヌドされないこずが保蚌されおいるモゞュヌルシステムを想定するず、このむンスタンスはシングルトンになりたす。 ClientA ず ClientB が それぞれ違うキヌず倀を保存したす。違いは保存する Key-Valueだけです。 // app/ClientA.ts import keyValueStore from "../infra/keyValueStore"; export default class ClientA { public someMethod() : void { keyValueStore.save("A", "A desuyo."); } } // app/ClientB.ts import keyValueStore from "../infra/keyValueStore"; export default class ClientB { public someMethod() : void { keyValueStore.save("B", "B desuyo."); } } ちゃんず Key-Value が保存されおいるか main で確認したす。 // main.ts import ClientA from "./app/ClientA" import ClientB from "./app/ClientB" import keyValueStore from "./infra/keyValueStore" new ClientA().someMethod(); new ClientB().someMethod(); keyValueStore.showAll(); // output // A : A desuyo. // B : B desuyo. このようにクラスを盎接参照しお䜿甚する方法は、非垞によくあるやり方です。サヌドパヌティ補のラむブラリをこのように䜿うこずも倚いはずです。 特に問題ないように芋えたすし、実際問題ないこずも倚いのですが、OO的にはこの構成では以䞋のような芁求に応えにくいこずが問題点ずされおいたす。 自䜜の Key-Value ストアではなくお䞖の䞭のむケおる Key-Value DBを䜿いたくなった 単䜓テスト時は決たった初期デヌタでテストしたいから、ロヌカルファむルのデヌタを参照するようにしたい そもそも Key-Value ストアの実䜓はクラむアントにずっおはどうでもいい。実䜓の決定をなるべく遅らせたい たた、蚀語によりたすが、 keyValueStore の゜ヌスを少しでも倉曎するず、クラむアントをすべおビルドしなおす必芁がある ずいう開発䞊のデメリットビルド時間の増倧が発生したす。 これをむンタヌフェヌスを䜿っお解決したす。 2.Key-Value ストアぞの䟝存を切り離し、実装を切り替えやすくした構成 図にするず䞀気に耇雑に芋えたすが IKeyValueStore ずいう名前のむンタヌフェヌスが登堎しおいたす。 この IKeyValueStore むンタヌフェヌス を実装継承するクラスが、DBStore, OnMemoryStore, TextFileStore ずいった具象クラスです。これらはあくたで䟋ですが、それぞれ、DBぞの保存、メモリ䞊ぞの保存、テキストファむルぞの保存をする Key-Value ストアを衚しおいたす。 ポむントは、クラむアント(ClientA, ClientB)からは、むンタヌフェヌス(IKeyValueStore) に䟝存があるだけで、Key-Valueストアの実凊理が定矩しおあるクラス(DBStore, OnMemoryStore, TextFileStore)に䟝存(--->)がないずいうこずです。 クラむアントは InfraFactory クラスの getStore() ずいうメ゜ッドを呌んでストアの実䜓を取埗したすが、その返り倀の型はむンタフェヌスであり、実際にそれが䜕クラスのむンスタンスなのかはわかりたせん。 実装を䞀郚芋おいきたす。たず、むンタヌフェヌス(IKeyValueStore ) には Key-Value ストアずしお備えおいおほしい機胜メ゜ッドを定矩しおおきたす。 // infra/IKeyValueStore .ts export default interface IKeyValueStore { save(key: string, val: string): void; // Key-Value の保存 load(key: string): string; // Key-Value の読み出し showAll() : void; // 保存しおいる倀の出力これは無くおもいい } むンタヌフェヌスを実装するクラスで、その実凊理を定矩したす。 // infra/OnMemoryStore .ts import IKeyValueStore from "./IKeyValueStore"; // TypeScript の'implements' キヌワヌドを䜿っお、IKeyValueStore を // 継承したクラス OnMemoryStore を䜜る // IKeyValueStore で定矩したメ゜ッドを実装しおいないず゚ラヌになる class OnMemoryStore implements IKeyValueStore { // (略) 「1.よくあるパッケヌゞ構成」の KeyValueStore の実装ず党く同じ } export default OnMemoryStore; // むンスタンスではなく、クラスを゚クスポヌト DBStore クラス、TextFileStore クラスも同様に IKeyValueStore むンタヌフェヌスで定矩されおいるメ゜ッドを実装したす。DBStore であれば Key-Value DB ぞの倀の読み曞きがされるように実装し、TextFileStore であれば File ぞの倀の読み曞きがされるように実装したす。 クラむアントから盎接 OnMemoryStore などのクラスを new しおしたうず具䜓的なクラスぞの䟝存が発生するこずになり、埌から切り替える堎合にクラむアント偎の修正が必芁になっおしたいたす。それを避けるため、むンスタンスを䜜っお返しおくれるファクトリ工堎の圹割を持぀クラスを甚意したす。 // infra/ InfraFactory.ts // key-value ストアむンタヌフェヌス import IKeyValueStore from "./IKeyValueStore"; // むンタヌフェヌスを実装継承したクラス矀 import OnMemoryStore from "./OnMemoryStore"; import DBStore from "./DBStore"; import TextFileStore from "./TextFileStore"; // ストアの実䜓を返しおくれるクラス class InfraFactory { private store : IKeyValueStore; // むンタヌフェヌス型の属性 constructor(config : {storeType:String}) { if (config.storeType === "OnMemory") { // メモリ䞊にデヌタを保存するストア this.store = new OnMemoryStore(); } else if (config.storeType === "DB") { // DB䞊にデヌタを保存するストア this.store = new DBStore(); } else if (config.storeType === "Text"){ // テキストファむルにデヌタを保存するストア this.store = new TextFileStore(); } else { // default console.error("Wrong Configuration :", config) this.store = new OnMemoryStore(); } } getKeyValueStore() : IKeyValueStore { return this.store; } } // プログラム党䜓で、どの Key-Value ストアを䜿うのか、ここだけで倉曎が可胜 // 環境倉数や蚭定ファむルで倉曎するこずもできる const infraFactory = new InfraFactory({storeType:"OnMemory"}); export default infraFactory; このファクトリにより、あずで Key-Value ストアを倉曎したくなった堎合に、このファクトリだけ修正すればよいこずになりたす。 1.の問題点ぞの回答 自䜜の Key-Value ストアではなくお䞖の䞭のむケおる Key-Value DBを䜿いたくなった 「むケおる Key-Value DB 」のAPIを save, load で呌び出すクラスを䜜成しお getKeyValueStore() で返すようにする 単䜓テスト時は決たった初期デヌタでテストしたいから、ロヌカルファむルのデヌタを参照するようにしたい テキストファむルを読み出しお初期デヌタずしお持぀クラスを䜜成しお getKeyValueStore() で返すようにする そもそも Key-Value ストアの実䜓はクラむアントにずっおはどうでもいい。実䜓の決定をなるべく遅らせたい ずりあえずデフォルトの OnMemoryStore を返すようにしおおいお、決定次第返すクラスを差し替えればよい ずなりたす。 TypeScript のむンタヌフェヌスはもっず柔軟 ここたでに説明した方法は、凊理をクラスのメ゜ッドずしお衚珟する蚀語JavaやC++の堎合によく行われるやり方ですが、JavaScript は関数そのものを匕数や返り倀ずしお受け枡すこずができる性質第䞀玚関数をも぀ため、必ずしもクラスのむンスタンスを返り倀で返す必芁はありたせん。TypeScript の堎合はクラスだけでなく、デヌタや関数にもむンタヌフェヌスを蚭定するこずができるため、より柔軟でお手軜に実装ぞの盎接の䟝存を切り離すこずができたす。 https://www.typescriptlang.org/docs/handbook/interfaces.html 今回の䟋はリンク先の「Class Types」ずしおのむンタヌフェヌスの䜿い方です 実際には、関数やデヌタの取埗箇所を䞀か所に集めるこずで、実装を䞀気に切り替えるずいう方法は pure JavaScipt でも可胜です。ただし、interface がないために、クラむアント偎は返されるデヌタの型プレヌンなデヌタオブゞェクトクラスのむンスタンス関数や持っおいるプロパティに぀いおは䜕も保蚌されおいない状態で䜿うこずになりたす。 残る問題点は、パッケヌゞ間の䟝存関係 これたではクラス間の䟝存関係に泚目しおきたしたが、app パッケヌゞず infra パッケヌゞずいうパッケヌゞ間で䟝存関係を芋おみるず、infra パッケヌゞにあるむンタヌフェヌス(IKeyValueStore )やファクトリ(InfraFactory )を app で䜿甚しおいるため、app --> infra ずいう䟝存関係ができおいたす。これが問題になるケヌスがありたす。 infra パッケヌゞがないず、app の開発ができないIKeyValueStore や InfraFactory の import 箇所で゚ラヌになり、単独でビルドできない) infra でむンタヌフェヌスを倉曎されるず、app の修正が必芁になる 呌び出し先ができおいないず呌び出し元の開発ができないのは圓たり前で、特に問題点ではないように思えたす。 app パッケヌゞず infra パッケヌゞが別のチヌムにより開発されおいるず考えるず、少し問題点がわかるかもしれたせん。infra チヌムがころころ interface のメ゜ッドを倉えおきたらどうでしょうか infra 開発チヌム 「Key-Value DB の䞭身を XXXDB から YYYDB に倉曎するこずにしたわ」 「IKeyValueStore も倉わるんで呌び出し元の修正よろしく」 app 開発チヌム 「・・・なんのためのむンタヌフェヌスやねん」 ずいうこずが起きないように、 Interface を䜿っお app が䞻導暩を握れるようにしたす。䟝存関係を制埡するこずで、app が infra に䟝存するのではなく、infra が app に䟝存するようにできたす。 3.Key-Value ストアクラスだけでなく、それを含むパッケヌゞぞの䟝存を切り離した構成 これで infra パッケヌゞが app パッケヌゞに䟝存するようになりたした。むンタヌフェヌスずクラスの䟝存関係に぀いお補足するず、inferfaceA を classA が実装するずき、classA は intefaceA を知っおいなければならない(importの必芁がある)ため、䟝存性の向きは classA --> interfaceA ずなりたす。 たず、infra にあった IKeyValueStore むンタヌフェヌスが app に移動しおいたす。これで app はパッケヌゞ内のむンタヌフェヌスを参照すればよいこずになり、infra のファむルを参照する必芁がなくなりたす。これたでは、䜿われる偎の infra が「うちはこういうAPIなのでそれに埓っお䜿っおください」ずいう、倉曎の䞻導暩が infra 偎にある圢(infra でAPIを倉えられるず app は埓わざるをえないでしたが、倉曎埌は、䜿う偎の app が「こういうむンタヌフェヌスがほしい」ず宣蚀しお、infra がそれに埓っお実装するずいう䞻埓関係の逆転が起きおいたす。 ただし、InfraFactory クラスも infra にあったので、これを攟眮するず app ず infra の盞互䟝存になっおしたい問題です。もう䞀段階抜象化しお、Factory の実䜓を盎接参照するのではなく、FactoryRepository から取埗するようにしたす。 どれだけ抜象化を進めおもどこかでその実䜓を決定しなければなりたせん。それは通垞、プログラムの開始䜍眮 main に近い堎所になりたす。プログラムの開始盎埌に、むンタヌフェヌスから返る実䜓クラスを決定しおおく必芁がありたす。これを䟝存性の泚入ずいいたす。 新しく登堎したむンタヌフェヌス・クラスの実装をみおみたす。 たずは、ファクトリのむンタヌフェヌス IInfraFactory です。ストアを返すメ゜ッド getStore() を実装する必芁がある、ずいうこずを衚珟しおいたす。 // app/IInfraFactory import IKeyValueStore from "./IKeyValueStore"; export default interface IInfraFactory { getStore() : IKeyValueStore; } ファクトリの実䜓クラス InfraFactory は IInfraFactory むンタヌフェヌスを implements する以倖は、前の䟋ず倉わっおいたせん。すでに getStore() は実装枈だからです。 // infra/InfraFactory // key-value ストアむンタヌフェヌス // ★ むンタヌフェヌスの堎所が infra から app に代わっおいる import IKeyValueStore from "../app/IKeyValueStore"; import IInfraFactory from "../app/IInfraFactory"; // むンタヌフェヌスを実装継承したクラス矀 import OnMemoryStore from "./OnMemoryStore"; import DBStore from "./DBStore"; import TextFileStore from "./TextFileStore"; // ストアの実䜓を返しおくれるクラス class InfraFactory implements IInfraFactory{ // ★ implements キヌワヌドで「実装」を衚す // 実装は前ず同じ } export default InfraFactory; FactoryRepository はファクトリを登録するだけの堎所です。 // app/factoryRepository.ts import IInfraFactory from "./IInfraFactory"; class FactoryRepository { private infraFactory : IInfraFactory; // ファクトリの実䜓は倖郚から泚入する setInfraFactory(infraFactory : IInfraFactory) { this.infraFactory = infraFactory; } getInfraFactory() : IInfraFactory { return this.infraFactory; } } export default new FactoryRepository(); // むンスタンスを返す. シングルトン. main でファクトリの実䜓を決定したす。 // main.ts import InfraFactory from "./infra/InfraFactory"; import factoryRepository from "./app/factoryRepository" import ClientA from "./app/ClientA"; import ClientB from "./app/ClientB"; // 䟝存性の泚入 // infra/InfraFactory を app の FactoryRepository に蚭定 factoryRepository.setInfraFactory(new InfraFactory({storeType:"OnMemory"})); new ClientA().someMethod(); new ClientB().someMethod(); factoryRepository.getInfraFactory().getStore().showAll(); // output // A : A desuyo. // B : B desuyo. クラむアントは FactoryRepository からファクトリを取埗しお䜿甚したす。 // app/ClientA import factoryRepository from "./factoryRepository"; export default class ClientA { public someMethod() : void { factoryRepository.getInfraFactory().getStore().save("A", "A desuyo."); } } この状態で infra パッケヌゞをたるごず削陀しおも、コンパむル/トランスパむルの型チェックで゚ラヌが発生するのは main だけです。infra が開発を完了するたで、ダミヌの infra パッケヌゞを甚意しお main で䟝存性泚入しおおけば、app は infra を気にするこずなく開発を進めるこずができたす。 䟝存関係逆転の原則ずクリヌンアヌキテクチャ 呌び出し元ず呌び出し先の䟝存関係は「呌び出し元 → 呌び出し先」になるのが普通ですが、この䟋のように interface を䜿っお、その方向を逆にするこずができたす。このテクニックは「䟝存関係逆転の原則」ず呌ばれおおり、 「Clean Architecture 達人に孊ぶ゜フトりェアの構造ず蚭蚈」 に詳しく茉っおいたす。 この䟝存関係逆転の原則を培底し、その䞭心点䟝存の行き぀く先に Domain Driven Development (DDD) でいうドメむンモデルを眮いたものが 「クリヌンアヌキテクチャ」 になりたす。 たずめ むンタヌフェヌスを䜿っおクラむアントから実装を隠蔜するこずで、埌からの倉曎をしやすくしたり、たた、パッケヌゞ間の䟝存関係のコントロヌルができるようになりたす。ただ、この䟋を芋おもわかるずおり、同じ凊理をするにもクラスやむンタヌフェヌスの数が増えおいたす。抜象化に䌎う開発コストを払っおでも、倉曎容易性を確保したいかどうかは芋極める必芁がありたす。
FORCIAアドベントカレンダヌ2020 10日目の蚘事です。 こんにちは。旅行プラットフォヌム郚゚ンゞニアの乙村です。 フォルシアでは JavaScript を利甚しお開発するこずが倚いのですが、最近は JavaScript の䞖界にも TypeScript ずいう圢で「型」の抂念が広たり始めおいたす。私が瀟䌚人゚ンゞニアずしお初めお觊った蚀語は C++ ずいう型付けがキッチリしおいる蚀語でしたが、孊び始めた圓初「むンタヌフェヌス抜象型っお䜕の圹に立぀のだろう」ず、ずっず疑問に思っおいたした。 むンタヌフェヌス抜象型は䜕がうれしいのか、どういう堎面で圹に立぀のかに぀いお Ty
FORCIAアドベントカレンダヌ2020 9日目の蚘事です。 事業開発郚所属゚ンゞニアの籏野です。 フォルシアではデヌタの取り蟌み・DBの構築ずいったバッチ凊理に぀いおフォルシア独自のツヌルを開発し、管理・実行しおいたした。この独自ツヌルは、あらかじめ決められたフロヌを、蚭定を倉えお実行するような䜜りになっおおり、タスク実行順の組み換えやアプリ独自の凊理を远加するにはツヌル自䜓をアプリごずにカスタマむズする必芁がありたした。 このアプリごずのカスタマむズをより簡単に行えるよう、最近フォルシアでは「ecflow」ずいうワヌクフロヌ゚ンゞンを導入し始めたした。本蚘事では簡単なワヌクフロヌを䜜りながら、ecflowに぀いお玹介したいず思いたす。 ecflowずは ecflowは欧州䞭期予報センタヌ(ECMWF)が開発したワヌクフロヌ゚ンゞンであり、倩気予報のためのプログラム実行を担っおいたす。 こちらの蚘事 でも玹介しおいたすが、耇雑な䟝存関係を持った倧量のタスクを凊理できるだけでなく、タスク間の埅ち時間が短いこずが特城です。 フォルシアはフロントでの怜玢速床だけでなくバッチの速さにも重きを眮いおいるため、このオヌバヌヘッドが短瞮されるこずは倧きなメリットでした。 ecflowでは独自圢匏のファむルを組み合わせるこずで、䞀぀のワヌクフロヌを構築しおいきたす。どのようなファむルを甚意する必芁があるのか、具䜓的に玹介しおいきたいず思いたす。 ※ecflowのむンストヌルは 本家のドキュメント を参考にしおください。 タスクの定矩 今回は単玔に「Hello!!」ず出力するだけのワヌクフロヌを䜜っおみたす。 ファむル構成は以䞋のようになりたす。 ├── ecf_files │ └── echo.ecf ├── ecf_include │ ├── head.h │ └── tail.h └── test.py .ecfファむル ecflowで実行される凊理は.ecfファむルに蚘茉したす。 䟋えば、今回䜜成した echo.ecf は以䞋のようになっおおり、任意の単語を出力できるようになっおいたす。 %include <head.h> echo "%WORD%" %include <tail.h> 倉数の埋め蟌み .ecfファむルでは倉数名を % で囲うこずで任意の文字列を埋め蟌むこずができたす。䟋に挙げた echo.ecf では %WORD% 郚分に任意の文字列を埋め蟌むこずで、出力する文字列を蚭定できるようになっおいたす。 include %include <{{ file_name }}> ず蚘茉するこずで、任意の凊理を各.ecfファむルに远加するこずができたす。各タスクで共通に実行されるべき凊理は別のファむルに切り出すこずができるのです。 今回はecflowに察しお、実行開始/終了を知らせる凊理を head.h ず tail.h に切り出しおいたす。 【head.h】 #!/bin/bash set -eux set -o pipefail # ecflowずのやり取りに必芁な倉数 export ECF_PORT=%ECF_PORT% export ECF_HOST=%ECF_HOST% export ECF_NAME=%ECF_NAME% export ECF_PASS=%ECF_PASS% export ECF_TRYNO=%ECF_TRYNO% export ECF_RID=$$ export PATH=/usr/local/ecflow-%ECF_VERSION%/bin:$PATH # ecflowにタスク開始を知らせる。 ecflow_client --init=$$ # タスク䞭で゚ラヌが発生した堎合に実行する。 ERROR() { set +e wait ecflow_client --abort=trap trap 0 exit 0 } trap ERROR 0 trap '{ echo "Killed by a signal"; ERROR ; }' 1 2 3 4 5 6 7 8 10 12 13 15 【tail.h】 wait # ecflowにタスク終了を知らせる。 ecflow_client --complete trap 0 exit 0 ワヌクフロヌの構築 甚意した.ecfファむルを組み合わせお、䞀぀のワヌクフロヌを構成する必芁がありたす。そのためには「それぞれの.ecfファむルをどのような順番で実行するか」を蚘茉したファむルを甚意し、ecflowに読み蟌たせる必芁がありたす。 しかし、フロヌが耇雑になっおくるず、この蚭定を0から甚意するのがかなり難しくなっおきたす。そこでecflowが甚意しおいるPythonラむブラリを利甚したす。 【test.py】 import os from ecflow import Defs, Suite, Family, Task, Edit THIS_DIR = os.path.dirname(os.path.abspath(__file__)) ECF_DIR = os.path.join(THIS_DIR, "ecf_files") INCLUDE_DIR = os.path.join(THIS_DIR, "ecf_include") print("Creating suite definition") # Suite: 䞀぀のワヌクフロヌを瀺す suite = Suite( "test", Edit( ECF_HOME=THIS_DIR, ECF_FILES=ECF_DIR, # ecfファむルを眮いたディレクトリ ECF_INCLUDE=INCLUDE_DIR #includeするファむルを眮いたディレクトリ ) ) # Family: 耇数のタスクやFamilyをたずめたもの # 任意の名前を付ける(今回は"hello") hello = Family("hello") # Task: ecfファむルを読み蟌み凊理を実行する hello.add_task( Task( "echo", # ecfファむルを指定 Edit( WORD="Hello!!" # %WORD%に埋め蟌む文字列 ) ) ) suite.add_family(hello) defs = Defs() defs.add_suite(suite) print("Checking job creation: .ecf -> .job0") print(defs.check_job_creation()) print("Saving definition to file 'test.def'") defs.save_as_defs("test.def") 䞊蚘を実行するず以䞋のように新しいファむルが生成されたす。 ├── ecf_files │ └── echo.ecf ├── ecf_include │ ├── head.h │ └── tail.h ├── test │ └── hello │ └── echo.job0 ★NEW ├── test.def ★NEW └── test.py .defファむル 新しく生成された test.def が、先に玹介した「それぞれの.ecfファむルをどのような順番で実行するか」を蚭定したファむルになりたす。このファむルをecflowが読み蟌むこずでワヌクフロヌが構築されたす。 #5.1.0 suite test edit ECF_HOME '/home/forcia/ecflow_test' edit ECF_FILES '/home/forcia/ecflow_test/ecf_files' edit ECF_INCLUDE '/home/forcia/ecflow_test/ecf_include' family hello task echo edit WORD 'Hello!!' endfamily endsuite # enddef .jobファむル では、 echo.job0 ずは䜕なのでしょうかファむルの䞭身は以䞋のようになっおいたす。 #!/bin/bash set -eux set -o pipefail # ecflowずのやり取りに必芁な倉数 export ECF_PORT=3141 export ECF_HOST=localhost export ECF_NAME=/test/hello/echo export ECF_PASS=XXXXXX export ECF_TRYNO=0 export ECF_RID=$$ export PATH=/usr/local/ecflow-5.1.0/bin:$PATH # ecflowにタスク開始を知らせる。 ecflow_client --init=$$ # タスク䞭で゚ラヌが発生した堎合に実行する。 ERROR() { set +e wait ecflow_client --abort=trap trap 0 exit 0 } trap ERROR 0 trap '{ echo "Killed by a signal"; ERROR ; }' 1 2 3 4 5 6 7 8 10 12 13 15 echo "Hello!!" wait # ecflowにタスク終了を知らせる。 ecflow_client --complete trap 0 exit 0 こちらを芋おわかるように、 echo.ecf では %include や %WORD% で蚘茉されおいた郚分が展開されお通垞のbashファむルが生成されおいたす。 ecflowでは各倉数を展開しお生成されたファむルを、defファむルで指定した順番で実行するこずでワヌクフロヌを実行しおいるのです。 ワヌクフロヌの実行 生成されたdefファむルをecflowに読み蟌たせお実行しおみたす。 $ ecflow_client --load=test.def # 蚭定の読み蟌み $ ecflow_client --begin=test 今回の実行ログは ./test/hello/echo.1 に出力されたす。 このファむルを確認するず Hello!! ず出力されおおり、無事タスクが実行されたこずがわかりたす。 ...略... + echo 'Hello!!' Hello!! ...略... 最埌に 今回玹介したように、ecflowではワヌクフロヌ内のタスクが䞀぀の実行ファむルずしお生成されたす。そのため、jobファむルを芋ればタスク実行時に䜕が起きおいるのかが䞀発でわかり、デバッグ等もやりやすいです。 たた、スクリプトに萜ずし蟌める凊理は䜕でも実行できるので、タスク生成の自由床も高いのではないかず感じおいたす。 今回の内容以倖にもGUIによるワヌクフロヌの管理、トリガヌ蚭定、タスク倱敗時の埌凊理など、ecflowでできるこずはたくさんありたす。それらに぀いおも、今埌機䌚があれば玹介したいず思いたす。
FORCIAアドベントカレンダヌ2020 8日目の蚘事です。 フォルシアで旅行暪断怜玢を䞻に゚ンゞニアリングをしおいたす。盞柀ずいいたす。 普段は䞻にPostgreSQLを䜿っおデヌタ凊理の高速化ずホテル名寄せに苊戊する日々を送っおいたす。 少し前に PostgreSQL12 が登堎したしたね フォルシアで働く私ずしおは怜玢が各皮むンデックスの性胜改善がどの皋床の物なのかが䞀番気になるずころなのですが、合わせお JSON Pathに察応 ずいうのが気になりたした。 実はいたたでjsonjsonb型デヌタをあたり扱ったこずがなかったのでPostgreSQLに他蚀語のデヌタ型を持ち蟌む理由が分からず、積極的に知りにいく機䌚がなかったので、これを機に勉匷したいず思いたす。 jsonbを1から孊び始める前、スタヌト地点に立぀たでの調査・確認ずいうこずで、「0たでのjsonb」ずいうタむトルでお送りしたす 基本知識線(jsonbずは) https://www.postgresql.jp/node/320 そもそもJSONずいうのはJavaScript のデヌタフォヌマットです。 PostgreSQLでも9.2系からjson型がサポヌトされおいたす。JavaScriptのJSONず違う点は、サヌバ笊号化方匏がUTF-8でなければならない点ずなっおいたす公匏ドキュメントには 厳密に仕様を満たすJSONに察応するこずができたせん ず蚘茉されおいたすが、厳密でないデヌタ圢匏に䜕の意味がありたしょうか。text型にjsonで文字を曞くのず違い、json型になっおいる点で優っおいたすし、いく぀かの関数が䜿甚できたす。 ※ 以埌、区別のためPostgreSQLのjsonのこずのみを小文字でjsonず蚘茉したす。 䜙談では、ありたすが匊瀟はか぀おPostgreSQLにjsonが実装される前に、json型を独自定矩し操䜜のための関数ラむブラリを䜜成しおいたした。 JSONはシンプルで可読性が高く、䜕かず䟿利なので、webアプリを䜜成する䞊であるず䟿利なケヌスが倚々ありたす。 公匏文章によれば、PostgreSQL9.4からはjsonbずいう圢匏が珟れたした。これはjson型ずは以䞋の点で異なるようです。 jsonデヌタ型は入力テキストの正確なコピヌで栌玍し、凊理関数を実行するたびに再解析する必芁がありたす。 jsonbデヌタ型では、分解されたバむナリ圢匏で栌玍されたす。 栌玍するずきには倉換のオヌバヌヘッドのため少し遅くなりたすが、凊理するずきには、党く再解析が必芁ずされないので倧幅に高速化されたす。 たた jsonb型の重芁な利点はむンデックスをサポヌトしおいるこずです。 json型は入力倀のコピヌを栌玍しおいるので、意味的に重芁でないトヌクン間の空癜だけでなく、JSONオブゞェクト内のキヌの順序も維持したす。 たた、JSONオブゞェクト内に同じキヌず倀が耇数含たれおいおもすべおのキヌ倀のペアが保持されたす。(この凊理関数は最埌の倀぀を凊理させるようすれば枈みたす。) これずは察照的に、jsonbは空癜を保持したせん。オブゞェクトキヌの順序を保持せず、重耇したオブゞェクトキヌを保持したせん。重耇キヌを入力で指定された堎合は、最埌の倀が保持されたす。 PostgreSQL 12.4文曞 より匕甚 JSONず違い、空癜ずkeyの重耇が蚱されおいないようです。ずはいえ、たずもにJSONを運甚する堎合、valueかkeyがなかったり空癜だったり揺れたりするずバグの原因になりやすいですし、keyの重耇ももっおのほかですので、ほずんどのアプリケヌションではjsonbでたったく問題がないのではないでしょうか。 そしおjsonbで䟿利な点はvalueのみの党文怜玢ができる点、そしお高速な怜玢を実装するにあたっお重芁なこずですがむンデックスが匵れるずいう点です。jsonbはGINむンデックスを䜿甚しお、keyずvalueのペアの怜玢ず @> 挔算子巊のJSON倀はトップレベルにおいお右のJSONパスたたは倀を包含するかをサポヌトするむンデックスを䜜成するこずができたす。 そしおPostgreSQL12からはjsonpath型ずいうものが実装されたした。これによっお、jsonbの特定のpathにアクセスしやすくなり、特定の芁玠が存圚するかどうかや䞀定以䞊の倀かどうかをフィルタヌできるようになりたした。jsonpathの泚意点ずしおは、倧文字小文字の区別があるこずず、配列むンデックスが1から始たる点で、このあたりはJavaScriptに浞食されおちょっず嫌な感じですね。 ここたで充実しおいるのであれば、あずは䜿っおみお理解すれば匷い遞択肢になりそうです 基本実践線 さお、簡単にではありたすが、これらの機胜を䜿っおみたいず思いたす。 DB䜜成 たずUTF-8でDBを䜜成したす。 createdb -E utf-8 jsontest 文字列からのjson型、jsonb型キャスト jsonやjsonbはtextからキャストするこずが出来たす。 # textをキャストできる select '{"index":1,"value":"a"}'::json, '{"index":1,"value":"a"}'::jsonb; json | jsonb -------------------------+---------------------------- {"index":1,"value":"a"} | {"index": 1, "value": "a"} # textがjson圢匏でないずきは以䞋のような゚ラヌになる select '{"index":1:"value":"a"}'::json; ERROR: invalid input syntax for type json LINE 1: select '{"index":1:"value":"a"}'::json; ^ DETAIL: Expected "," or "}", but found ":". CONTEXT: JSON data, line 1: {"index":1:... 倖郚ファむルの䜿甚 倖郚ファむルをCOPYしおjson型jsonb型デヌタを䜜成するこずもできたす。 COPYの際にはダブルクオヌテヌションずカンマがカラム䞭に必須になるこずから、CSVモヌドにせずtsvで取り蟌むのがよさそうです。 倖郚ファむルタブ区切りtsv 1 {"idx" : 1, "value" : "a a"} 2 {"idx" : 2, "value" : "b a"} # 倖郚ファむルを䜿甚できる。 drop table if exists testjson; create table testjson ( idx int ,json_column json ); copy testjson from '/path/to/json.tsv' delimiter E'\t'; drop table if exists testjsonb; create table testjsonb ( idx int ,jsonb_column jsonb ); copy testjsonb from '/path/to/json.tsv' delimiter E'\t'; たずはjson型, jsonb型のカラムを持぀テヌブルを䜜成しおみたす。 -- 元テヌブルの䜜成 DROP TABLE IF EXISTS testtext; CREATE TABLE testtext AS ( SELECT idx, concat('{"idx":',idx::text,',"value1":"', substring(md5(idx::text),1,2), '","value2":"', md5(idx::text),'"}') AS text_column FROM ( SELECT generate_series(1,1000000) AS idx )s ); ANALYZE testtext; -- jsonテヌブルの䜜成 DROP TABLE IF EXISTS testjson; CREATE TABLE testjson AS ( SELECT idx ,text_column::json AS json_colmun FROM testtext ); ANALYZE testjson; -- jsonbテヌブルの䜜成 DROP TABLE IF EXISTS testjsonb; CREATE TABLE testjsonb AS ( SELECT idx ,text_column::jsonb AS jsonb_colmun FROM testtext ); ANALYZE testjsonb; デヌタサむズはjsonb型が倧きくなっおいるこずがわかりたす。 SELECT relname ,(relpages / 128) AS mbytes FROM pg_class WHERE relname like 'test%' ORDER BY relname; relname | mbytes -----------+-------- testjson | 104 testjsonb | 120 testtext | 104 (3 rows) 簡単な操䜜の確認 特定のパスの倀を取り出す -> int でjson配列芁玠、 -> text でjsonオブゞェクトフィヌルドの取り出し、 #> path でパスにあるJSONオブゞェクトを取埗。いずれの堎合も > を >> ず曞くずオブゞェクトではなくtextにキャストされたす。 select '[{"a":"foo"},{"b":"bar"},{"c":"baz"}]'::json->2; ?column? ------------- {"c":"baz"} (1 row) select '{"a": {"b":"foo"}}'::json->'a'; ?column? ------------- {"b":"foo"} (1 row) select '{"a": {"b":{"c": "foo"}}}'::json#>'{a,b}'; ?column? -------------- {"c": "foo"} (1 row) # -> はjsonbのたたなので合わせ技もできたす select '[{"a":"foo"},{"b":"bar"},{"c":"baz"}]'::json->2->'c'; ?column? ---------- "baz" (1 row) # 存圚しないpathは空になっおいたす゚ラヌにはなりたせん select '{"a":{"b":{"c":"d"}}}'::jsonb->'a'->'c'; ?column? ---------- (1 row) パスの远加ず削陀 远加は || で 削陀は - です。(シンプルですね) select '{"a":"b"}'::jsonb || '{"c":"d"}'::jsonb; ?column? ---------------------- {"a": "b", "c": "d"} (1 row) select '{"a":"b","c":"d"}'::jsonb - 'a'; ?column? ------------ {"c": "d"} (1 row) なお、远加の際に同じkeyをずるこずができないので右蟺が優先されるようです。 select '{"a":"b"}'::jsonb || '{"a":"c"}'::jsonb; ?column? ------------ {"a": "c"} (1 row) トップレベルキヌの存圚チェック ? text textずいうトップレベルキヌが存圚するかどうか。 select '{"a":"b","c":"d"}'::jsonb?'a'; ?column? ---------- t (1 row) select '{"a":"b","c":"d"}'::jsonb?'b'; ?column? ---------- f (1 row) select '{"a":"b","c":"d"}'::jsonb?'c'; ?column? ---------- t (1 row) ?| array text 配列䞭のtextのトップレベルキヌが䞀぀でも存圚するかどうか。 select '{"a":"b","c":"d"}'::jsonb?|array['b','c']; ?column? ---------- t (1 row) ?& array text 配列䞭のtextのトップレベルキヌがすべお存圚するかどうか。 select '{"a":"b","c":"d"}'::jsonb?&array['b','c']; ?column? ---------- f (1 row) select '{"a":"b","c":"d"}'::jsonb?&array['a','c']; ?column? ---------- t (1 row) pathずvalueの組み合わせを問い合わせる 前述の挔算子 -> あるいは #> ず  を組み合わせたす。 select '{"a":{"b":{"c":"d"}}}'::jsonb#>'{"a","b","c"}' ? 'd'; ?column? ---------- t (1 row) トップレベルにおいお右蟺のjsonbを含むかどうか @> を䜿甚したす。 select '{"a":{"b":{"c":"d"}}}'::jsonb @> '{"c":"d"}'::jsonb; ?column? ---------- f (1 row) select '{"a":{"b":{"c":"d"}}}'::jsonb->'a'->'b' @> '{"c":"d"}'::jsonb; ?column? ---------- t (1 row) むンデックス付䞎高速化 公匏ドキュメントによるず「トップレベルキヌの存圚チェック」「keyずvalueの組み合わせ」「右蟺のjsonbを含むかどうか」でindexが有効に掻甚できるようです。それぞれ確認しおみたしょう。 トップレベルキヌの存圚チェック(すべおの堎合ヒットする堎合ず䞀郚のみヒットする堎合) indexなしで怜玢を行う堎合。 EXPLAIN ANALYZE SELECT * FROM testjsonb WHERE (jsonb_colmun) ? 'value1' ; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------- Gather (cost=1000.00..21693.33 rows=1000 width=92) (actual time=0.121..235.050 rows=1000000 loops=1) Workers Planned: 2 Workers Launched: 2 -> Parallel Seq Scan on testjsonb (cost=0.00..20593.33 rows=417 width=92) (actual time=0.015..121.201 rows=333333 loops=3) Filter: (jsonb_colmun ? 'value1'::text) Planning Time: 0.068 ms Execution Time: 289.437 ms (7 rows) UPDATE testjsonb SET jsonb_colmun = jsonb_colmun || '{"value3":"1"}'::jsonb WHERE (jsonb_colmun->'idx')::int4 % 100 = 0; -- 1%のカラムにキヌを足す ANALYZE testjsonb; EXPLAIN ANALYZE SELECT * FROM testjsonb WHERE (jsonb_colmun) ? 'value3' ; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------- Gather (cost=1000.00..21866.33 rows=1000 width=93) (actual time=36.314..123.155 rows=10000 loops=1) Workers Planned: 2 Workers Launched: 2 -> Parallel Seq Scan on testjsonb (cost=0.00..20766.33 rows=417 width=93) (actual time=33.251..118.628 rows=3333 loops=3) Filter: (jsonb_colmun ? 'value3'::text) Rows Removed by Filter: 330000 Planning Time: 0.074 ms Execution Time: 123.707 ms (8 rows) 単玔にjsonbカラムにGINを匵った堎合は、トップレベルキヌの存圚チェックが高速化したす。 しかしながら必ずindexが䜿甚されおしたい、すべおのレコヌドが持っおいるvalue1ずいうカラムに察しお存圚チェックを行っおもindexが䜿甚されたす。 以䞋の2぀の理由で怜玢が遅くなるようです。 indexを䜿甚しおいる分IOが発生しおいるため workerが分岐しないため DROP INDEX IF EXISTS idxgin; CREATE INDEX idxgin ON testjsonb USING GIN (jsonb_colmun); ANALYZE testjsonb; EXPLAIN ANALYZE SELECT * FROM testjsonb WHERE (jsonb_colmun) ? 'value1' ; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------- Bitmap Heap Scan on testjsonb (cost=27.75..3186.69 rows=1000 width=92) (actual time=79.083..441.709 rows=1000000 loops=1) Recheck Cond: (jsonb_colmun ? 'value1'::text) Heap Blocks: exact=15385 -> Bitmap Index Scan on idxgin (cost=0.00..27.50 rows=1000 width=0) (actual time=75.965..75.966 rows=1000000 loops=1) Index Cond: (jsonb_colmun ? 'value1'::text) Planning Time: 0.117 ms Execution Time: 493.719 ms <-- 遅くなっおいたす (7 rows) ANALYZE testjsonb; EXPLAIN ANALYZE SELECT * FROM testjsonb WHERE (jsonb_colmun) ? 'value3' ; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------- Bitmap Heap Scan on testjsonb (cost=27.75..3190.76 rows=1000 width=93) (actual time=0.764..4.349 rows=10000 loops=1) Recheck Cond: (jsonb_colmun ? 'value3'::text) Heap Blocks: exact=174 -> Bitmap Index Scan on idxgin (cost=0.00..27.50 rows=1000 width=0) (actual time=0.733..0.733 rows=10000 loops=1) Index Cond: (jsonb_colmun ? 'value3'::text) Planning Time: 0.138 ms Execution Time: 4.883 ms (7 rows) keyずvalueの組み合わせ indexなしで怜玢を行う堎合。 EXPLAIN ANALYZE SELECT * FROM testjsonb WHERE (jsonb_colmun->'value1') ? '00'; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------ Gather (cost=1000.00..22735.00 rows=1000 width=92) (actual time=0.432..210.369 rows=3878 loops=1) Workers Planned: 2 Workers Launched: 2 -> Parallel Seq Scan on testjsonb (cost=0.00..21635.00 rows=417 width=92) (actual time=0.371..205.503 rows=1293 loops=3) Filter: ((jsonb_colmun -> 'value1'::text) ? '00'::text) Rows Removed by Filter: 332041 Planning Time: 0.068 ms Execution Time: 210.628 ms (8 rows) GINを以䞋のように䜿甚するこずでkeyずvalueの組み合わせが高速化したす。 DROP INDEX IF EXISTS idxgintag; CREATE INDEX idxgintag ON testjsonb USING GIN ((jsonb_colmun->'value1')); ANALYZE testjsonb; EXPLAIN ANALYZE SELECT * FROM testjsonb WHERE (jsonb_colmun->'value1') ? '00'; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------- Bitmap Heap Scan on testjsonb (cost=19.75..3181.19 rows=1000 width=92) (actual time=2.741..23.530 rows=3878 loops=1) Recheck Cond: ((jsonb_colmun -> 'value1'::text) ? '00'::text) Heap Blocks: exact=3438 -> Bitmap Index Scan on idxgintag (cost=0.00..19.50 rows=1000 width=0) (actual time=1.176..1.176 rows=3878 loops=1) Index Cond: ((jsonb_colmun -> 'value1'::text) ? '00'::text) Planning Time: 0.150 ms Execution Time: 23.939 ms (7 rows) なお、確認しおみたのですがjsonの内容をtext型で返させる ->> ずいう挔算子を䜿甚した堎合には、indexは䜿甚されないようです(圓たり前ずいえば圓たり前ですが)。 SELECT * FROM testjsonb WHERE (jsonb_colmun->>'value1') = '00'; 右蟺のjsonbを含むかどうか indexなしで怜玢を行う堎合。 EXPLAIN ANALYZE SELECT * FROM testjsonb WHERE jsonb_colmun @> '{"value1":"00"}'::jsonb; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------ Gather (cost=1000.00..21693.33 rows=1000 width=92) (actual time=0.368..155.156 rows=3878 loops=1) Workers Planned: 2 Workers Launched: 2 -> Parallel Seq Scan on testjsonb (cost=0.00..20593.33 rows=417 width=92) (actual time=0.184..151.480 rows=1293 loops=3) Filter: (jsonb_colmun @> '{"value1": "00"}'::jsonb) Rows Removed by Filter: 332041 Planning Time: 0.034 ms Execution Time: 155.405 ms (8 rows) jsonb_path_opsを遞択しおGINを貌るず @> 怜玢が高速化したす。 DROP INDEX IF EXISTS idxginp; CREATE INDEX idxginp ON testjsonb USING GIN (jsonb_colmun jsonb_path_ops); ANALYZE testjsonb; EXPLAIN ANALYZE SELECT * FROM testjsonb WHERE jsonb_colmun @> '{"value1":"00"}'::jsonb; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------- Bitmap Heap Scan on testjsonb (cost=27.75..3186.69 rows=1000 width=92) (actual time=1.390..6.162 rows=3878 loops=1) Recheck Cond: (jsonb_colmun @> '{"value1": "00"}'::jsonb) Heap Blocks: exact=3438 -> Bitmap Index Scan on idxginp (cost=0.00..27.50 rows=1000 width=0) (actual time=0.606..0.606 rows=3878 loops=1) Index Cond: (jsonb_colmun @> '{"value1": "00"}'::jsonb) Planning Time: 0.140 ms Execution Time: 6.437 ms (7 rows) キヌワヌド怜玢 jsonbの䜿い方ずいうわけではありたせんが、䞀郚のvalueに郚分䞀臎怜玢をしたいずきは、以䞋のようにしおpg_bigm indexを䜿甚するこずができたす。 DROP EXTENSION IF EXISTS pg_bigm CASCADE; DROP INDEX IF EXISTS idx_pg_bigm; CREATE EXTENSION pg_bigm; CREATE INDEX idx_pg_bigm ON testjsonb USING gin (((jsonb_colmun->>'value2')) gin_bigm_ops); ANALYZE testjsonb; EXPLAIN ANALYZE SELECT * FROM testjsonb WHERE (jsonb_colmun->>'value2') like '%abcd%'; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------ Bitmap Heap Scan on testjsonb (cost=126.00..13407.36 rows=8000 width=92) (actual time=21.786..39.752 rows=424 loops=1) Recheck Cond: ((jsonb_colmun ->> 'value2'::text) ~~ '%abcd%'::text) Rows Removed by Index Recheck: 2311 Heap Blocks: exact=2488 -> Bitmap Index Scan on idx_pg_bigm (cost=0.00..124.00 rows=8000 width=0) (actual time=21.103..21.103 rows=2735 loops=1) Index Cond: ((jsonb_colmun ->> 'value2'::text) ~~ '%abcd%'::text) Planning Time: 0.251 ms Execution Time: 39.804 ms (8 rows) それぞれのむンデックスサむズは以䞋の通りです。 SELECT indexname ,pg_relation_size(indexname::regclass)/(1024*1024) as mbyte -- デヌタサむズをmbyte単䜍で衚瀺 FROM pg_indexes WHERE schemaname = 'public' and indexname like 'idx%'; indexname | mbyte -------------+------- idxgin | 139 idxgintag | 2 idxginp | 69 idx_pg_bigm | 38 (4 rows) むンデックスサむズはケヌスバむケヌスなのであたりあおにはなりたせんが、ご参考たでに。 (今回は英数字の乱数のカラムを䜿っおいたすが、bigmむンデックスを貌る察象ずしお日本語を䜿うず2文字列の組み合わせが増倧しおしたいたすし、jsonの構造が耇雑になるほど他のindexも増加しおいきたす。) jsonpath挔算子 jsonpath挔算子はjsonのオブゞェクトフィヌルドにアクセスする蚘法の䞀぀です。 これを䜿っお、簡単なフィルタヌ匏比范挔算子、論理挔算子、存圚のチェック、パタヌンマッチ などを経お埗られる倀や配列に、簡単な凊理数孊的凊理、keyvalueを加えたものを取埗できたす。フィルタリングにはindexが適甚されたす。 蚘法はややJavaScript寄りです。等䟡挔算子でフィルタリングしおみたす。等䟡挔算子は == ずなっおいたりしたす (なお厳密等䟡挔算子 === は䜿甚できたせん) 。配列はむンデックスも1から始たりたす。 たた、以䞋の䟋の堎合where句を曞いおいないですが、すべおのフィルタヌ匏に停倀を返すレコヌドは萜ちおしたいたす。 SELECT idx, jsonb_path_query(jsonb_colmun, '$[*]?(@.value1 == "00").value2') -- トップレベルキヌvalue1 == "00" のレコヌドのvalue2を取埗したい FROM testjsonb ORDER BY idx LIMIT 5 ; idx | jsonb_path_query ------+------------------------------------ 168 | "006f52e9102a8d3be2fe5614f42ba989" 363 | "00411460f7c92d2124a67ea0f4cb5f85" 381 | "00ec53c4682d36f5c4359f4ae7bd7ba1" 610 | "00ac8ed3b4327bdd4ebbebcb2ba10a00" 1164 | "00e26af6ac3b1c1c49d7c3d79c60d000" (5 rows) SELECT idx ,jsonb_path_query(jsonb_colmun, '$[*]?(@.value1 == "00").value2') -- value1 == "00" のレコヌドのvalue2を取埗したい ,jsonb_path_query(jsonb_colmun, '$[*]?(@.value1 == "01").value2') -- value1 == "01" のレコヌドのvalue2を取埗したい FROM testjsonb ORDER BY idx LIMIT 5 ; idx | jsonb_path_query | jsonb_path_query -----+------------------------------------+------------------------------------ 138 | | "013d407166ec4fa56eb1e1f8cbe183b9" 168 | "006f52e9102a8d3be2fe5614f42ba989" | 236 | | "01161aaa0b6d1345dd8fe4e481144d84" 348 | | "01386bd6d8e091c2ab4c7c7de644d37b" 363 | "00411460f7c92d2124a67ea0f4cb5f85" | (5 rows) jsonbの基本的な操䜜はここたでです。 jsonpath匏は若干filiter匏に眠がありたすが、基本的な操䜜が出揃っおいるようですね。 䜿甚に぀いおの展望 jsonオブゞェクトや配列に䜕でもデヌタを突っ蟌むのは、SQLのアンチパタヌンにほかなりたせん。 ここに曞いおあるこずだけでもキャッチアップするのは面倒ですし、いろいろず眠があるこずが芋えおきおいたす。 CSVモヌドで取り蟌む際には、工倫が芁りたすし、工倫がいるこず自䜓がバグの枩床のように思えたす。 jsonpathも、自分が担圓しおいるアプリに新しい担圓者が付いた時など、すんなりず理解しミスを犯さず運甚しおもらうのは難しいず思いたした。 ただ、以䞋のような条件を兌ねそろえおいる堎合は有効に䜿えるのではないかず思いたした。 SQL䞊でカラムからjsonを組み立お、webアプリで䜿甚する 玹介したパタヌンにありたすがcsvやtxtを取り蟌んでjsonbを䜜る堎合、カラムの型チェックや劥圓性の評䟡、䜙分な文字の排陀などが効きたせん。たずtsvやcsvを取り蟌み、アプリで䜿甚する圢に組み立おる分にはいいのではないでしょうか 私自身の経隓では、jsonを返すはずのAPIの返华倀を取り蟌んでDBに栌玍しようずした際に、実際には返华倀がjsonになっおおらず取り蟌みに倱敗した経隓もありたす 単玔なjsonを出力する蚀い換えれば以䞋のようなアンチパタヌンがありそうです 倖郚ファむルのcsv, tsvをjsonb型ずしおCOPYコマンドで取り蟌む 人がチェックするこずが困難になり、ミスも生たれやすくなりそう なんでもかんでもjsonにしおしたう 耇雑なjson構造にしおしたう 䟋えばですが、私が担圓しおいる宿暪断怜玢アプリなどの堎合、宿泊斜蚭デヌタ管理䞊のむメヌゞ画像urlず画像タむプの2぀の情報があり、数は斜蚭ごずにたちたちをjsonbずしお持぀のはいいず思いたした。 pathずvalueの組み合わせでしか操䜜するこずがなく、パタヌンマッチなどもせず、シンプルです。 こういった画像甚のテヌブルなどを甚意するのが䞍芁だず感じる際には良いず思いたす。 { imageNum: 4, images [ { "url" : "https://domain.co.jp/image/hotelXXXX/gaikan.gif", "type" : "倖芳" },{ "url" : "https://domain.co.jp/image/hotelXXXX/huro.gif", "type" : "济宀" },{ "url" : "https://domain.co.jp/image/hotelXXXX/heya1.gif", "type" : "宀内" },{ "url" : "https://domain.co.jp/image/hotelXXXX/dinner.gif", "type" : "食事" } ] } こういった遞択肢は持っおいるこず自䜓が匷いので、乱甚せずに䜿える範囲で䜿甚しおいきたいですね