- Customer Success
- Finance / Accounting
- バックエンドエンジニア
- Other occupations (2)
- Development
- Business
- Other
こんにちは、LOGILESSで業務委託として開発のお手伝いをしている山下です。週3でWebアプリケーションの機能追加・改善をしています。
本題の前にLOGILESSについて少し紹介させてください。
ECの市場が拡大していく中、ネットショップの運営は受注処理や在庫の管理、出荷作業など多くの業務を伴います。私たちLOGILESSはそれらの業務の効率化・自動化を行えるシステムを提供するスタートアップです。2020年6月末で物流会社45社と提携し、200社以上の企業にご利用いただいており、LOGILESSを利用した1ヶ月間の出荷は70万件を超えます。
ところで、ヘッダーの画像のキャラクタは弊社のマスコットキャラ「ロジごま」で、LOGILESSで様々な業務を自動化した結果、自由な時間を過ごせるようになったゴマアザラシです。表情や所作が愛くるしくて、ロジごまにはよく癒されています。ECの需要は伸びる一方、それを支える人手は不足し続けています。「私たちのプロダクトを通じて、ネットショップ運営の負担を少しでも減らしていきたい」そんな思いがロジごまには込められています。
ヘルプセンターの記事でお客様の疑問を解決する
LOGILESSでは毎日のように新しい機能の開発が行われ、業務の自動化・効率化を進んでいます。私たちのプロダクトを最大限に生かしていただくためにも、お客様に機能や仕様を認知・理解していただく必要があります。
そこで、私たちは機能や仕様に関する記事をまとめた「ヘルプセンター」を運用し、カスタマーサポートに努めています。
ヘルプセンター内の記事管理には「WordPress」を採用しており、AWS上でフルマネージドにWordPressを運用できるAMIMOTOにて運用しています。
Zendeskなどヘルプセンター機能を提供するSaaSが多くある中で、私たちがWordPressを選択した理由としては以下のとおりです。
- LOGILESSには「荷主」と「倉庫」の2つの利用者属性があり(そしてその両方を持っている利用者もいる)、柔軟な記事のカテゴライズが必要
- 膨大な記事を扱うため、インポート/エクスポートやバックアップなどの機能が必要
WordPressの検索機能の課題
WordPressでヘルプセンターを運用していくなかで、いくつか課題点がありました。
その中の1つとして「記事の検索体験が低い」ことが挙げられます。その要因としては2つの点が考えられます。
- あいまいなワードで検索できない
- 検索ワードに対する関連度を算出できない
WordPressでは仕様上、検索ワードに対して記事のタイトルや本文中の文字が完全一致していないと検索結果が表示されません。
例えば、「引当」というワードは「引き当て」「引当て」のように送り仮名が複数パターンあります。この場合、記事の表記が「引当」で統一されていた場合は残りの2パターンの送り仮名で検索した場合に検索結果が表示されません。これは、WordPressが記事データの永続化にMySQLを用いており、検索時にタイトル・本文に対してLIKE句のSQLでデータを取得しているためです。
さらに検索結果の表示順は投稿日時順となっています。そのため、探したい記事が検索結果の上位にくるとは限らず、検索結果の数が多い場合は探したい記事を見つけるまでに時間がかかってしまう可能性があります。
これらの検索における課題は、「探したい記事が見つからない、見つけられない」利用者からカスタマーサポートへの問い合わせが発生し、利用者の疑問点をその場で解消できない上、カスタマーサポートチームの対応リソースもかかります。
全文検索サービスの調査
検索における課題を解決するために、より柔軟に高度な検索ができる「全文検索サービス」をWordPressと連携させることにしました。全文検索サービスでは検索ワードに対して「Ngram」や「形態素解析」の自然言語処理が行われ、単語分割された上で検索されるため、あいまいなワードでも検索できるようになります。また、検索ワードの出現回数や頻度をスコアリングできるため、検索ワードに対して関連度の高い順に検索結果を取得することができます。
全文検索サービスはいくつか選択肢があり、私たちの要件に最適なものを採用するために以下の3つのサービスを調査し比較検討しました。
- Algolia
- Amazon Elasticsearch Service
- Amazon Cloud Search
Algolia
アメリカのスタートアップが提供する全文検索のSaaSでSlackやStripeでも採用されています。料金は無料から使えるのですが、検索回数やデータ連携回数に応じて料金が発生することが特徴的です。
【長所】
- Web APIやSDKが充実しており、シンプルに設計されているため少ないコード量で導入できる
- SaaSであるためインフラの料金や運用を気にしなくて済む
Web APIやSDKに関しては全文検索機能を提供するのはもちろん、検索ボックス・検索結果の表示などUIのライブラリも提供されています。フロントエンドからバックエンドまで全体を通しての開発工数が少なくなるよう開発者ファーストな設計になっています。
【短所】
- 利用頻度が高い or 利用したい機能によっては費用がかさんでしまう
- 検索対象のデータ構造がフラットである必要があり、ネストしたデータ構造を保持できない
Algoliaは検索回数やデータの連携回数に応じて利用料金が発生します。また、利用プランによっては機能が制限されてしまうため利用したい機能によってはコストがかさむ可能性があります。
ネストしたデータ構造を保持できないとは、以下のデータ構造の中のauthorやrelatedArticlesのようなデータの階層が深くなってしまう構造を指します。
const article = {
title: "タイトル1",
content: "本文1",
author: {
name: "著者",
age: 30,
},
relatedArticles: [
{title: "タイトル2", content: "本文2"},
{title: "タイトル3", content: "本文3"},
{title: "タイトル4", content: "本文4"},
]
}
Amazon CloudSearch
Solrをベースとした検索エンジンをAWS上でフルマネージドに利用できる全文検索サービスです。料金はAlgoliaとは異なりインスタンスの稼働時間に応じて発生します。最小スペックのインスタンスを1ヶ月フル稼働させた場合は約6,500円/月となります。
【長所】
- フルマネージドなのでスケーリングなどを考慮しなくてよく運用が楽
【短所】
- 検索対象のデータ構造がフラットである必要があり、ネストしたデータ構造を保持できない(Algoliaと同じ)
Amazon Elasticsearch Service
OSSの全文検索エンジンElasticsearchをAWS上にデプロイ・運用・スケールできるサービスです。料金はAmazon CloudSearchとは異なりインスタンスの稼働時間に応じて発生します。最小スペックのインスタンスを1ヶ月フル稼働させた場合は約2,200円/月となります。
【長所】
- AWSの他サービスと連携することによって機能の拡張性が高められる
- ネストしたデータ構造を保持でき、設定や辞書登録の自由度も高いため柔軟な検索が可能
- Kibanaが利用でき分析も行える
【短所】
- 自動スケールに対応していないため、インスタンスの負荷に応じたスケーリングの運用コストが発生する
- AlgoliaやAmazon CloudSearchに比べて設定や手順が多く利用開始までに時間がかかる
比較表
- LOGILESSがAWSを全面的に採用しているため、同じAWSで運用できることが望ましい
- 検索精度の向上を重視したいので、柔軟な検索が可能で、機能の拡張性が高い必要がある
これらを考慮して、私たちはAmazon Elasticsearch Serviceを導入することにしました。
Amazon Elasticsearch Serviceの導入
以下のステップで導入を進めました。
- インスタンスのセットアップ
- Wordpressプラグイン作成
- データ連携
インスタンスのセットアップ
AWSコンソールよりElasticsearch Serviceのダッシュボードを開き、利用を開始します。「新しいドメインの作成」よりインスタンス情報を設定することでセットアップが完了します。
Wordpressプラグイン作成
導入後のシステム構成は図のようになり、WordPressからElasticserachに対して「記事検索」「記事データ同期」を行えるようにする必要があります。これらの機能をWorpPressに組み込むためにWordPressプラグインを作成していきます。
全ての実装を記すと長くなるため、主要なコードのみ記載します。また、コードの見通しをよくするため実際のコードより簡素化しています。言語はWordPressのためPHPとなります。
Elasticsearchクライアントの初期化
Elasticsearchのクライアントとしては、elasticsearch-phpを用いています。Amazon Elasticsearch Serviceのエンドポイントとポートを指定し、クライアントの作成します。
private function createClient($options){
if (empty($options['endpoint']) || empty($options['port'])) {
return null;
}
$hosts = ['https://' . $options['endpoint'] . ':' . $options['port']];
return ClientBuilder::create()
->setHosts($hosts)
->build();
}
記事データの同期
MySQLから記事データを全件取得し、Elasticsearchへ連携します。どのようなデータを連携するかを$params
に設定しています。このコードでは記事のタイトルと本文のみを連携しています。
public function dataSync(){
try {
$options = get_option('wpels_settings');
$client = $this->createClient($options);
if (is_null($client)) {
throw new Exception('could not create client');
}
$posts = get_posts(array('posts_per_page' => -1, 'post_type' => "post"));
foreach ($posts as $post) {
$params = [
'index' => 'インデックス名',
'type' => 'post',
'id' => (string)$post->ID,
'body' => [
'title' => (string)$post->post_title,
'content' => (string)strip_tags($post->post_content),
]
];
$client->index($params);
}
return true;
} catch (Exception $e) {
$err = new WP_Error('Elasticsearch Mapping Error', $e->getMessage());
return $err;
}
}
記事の検索
検索ワードから記事を検索します。$params
の以下のパラメータに設定を行います。
query
: 検索対象のフィールドや条件を設定します。sort
: socreを指定することで検索ワードに対して関連度が高い順に並び替えが行えます。size
: 取得する検索結果の最大数を指定します。
public function search($search_query){
try {
$options = get_option('wpels_settings');
$client = $this->createClient($options);
$params = [
'index' => 'インデックス名',
'body' => [
'query' => [
'bool' => [
'should' => [
['match' => ['title' => $search_query,]],
['match' => ['content' => $search_query,]]
]
]
]
],
'sort' => ['_score'],
'size' => 100,
];
$results = $client->search($params);
$post_ids = array();
if (isset($results['hits']['hits'])) {
foreach ($results['hits']['hits'] as $hit) {
$post_ids[] = $hit['_id'];
}
}
return $post_ids;
} catch (Exception $e) {
$err = new WP_Error('Elasticsearch Search Error', $e->getMessage());
return $err;
}
}
データ連携
作成したプラグインをAMIMOTOへ導入します。導入後は図のように、WordPressの管理画面からElasticsearchへ連携できるようになりました。記事データは400を超えていましたが連携は1秒ほど終わり、スムーズに連携が完了しました。
Elasticsearch導入前後での比較
あいまい検索
「引当」には「引き当て」や「引当て」といった複数の送り仮名があることについて触れましたが、LOGILESSのヘルプセンターの記事では基本的に「引当」という送り仮名で表記を統一しています。そのため「引当て」や「引き当て」というワードで検索すると以前は検索結果が表示されませんでした。しかし、Elasticsearch導入後は図のように検索できるようになりました。
検索結果の表示順
WordPressでの検索結果は記事の公開日順に表示されることについて触れました。そのため、記事中に含まれることが多いワードで検索した場合、記事を探しづらいという問題がありました。
わかりやすい例が「在庫 自動連携」という検索ワードです。LOGILESSでは在庫の管理や自動連携をコアな機能として提供しているため、様々な記事にこのワードは登場します。お客様がこのワードで検索されるときは在庫の自動連携についてLOGILESS全体での基本的な仕様や設定方法を知りたいと考えられます。
しかし、WordPressの検索では図のように「ポンパレモール」や「EC Force」といった各プラットフォームについての個別詳細な説明をした記事が上位に表示されていました。これはWordPress(MySQL)で検索結果が記事の更新日順に並び替えられていたからです。
一方、Elasticsearch導入後はLOGILESS全体での基本的な仕様や設定方法が記載されている記事が上位に表示され、探したい記事を見つけやすくなりました。これはElasticsearchが検索ワードの記事内の出現頻度や回数をもとに検索結果の並び替えが行っているため、検索ワードに対してより関連度の高い記事が上位に表示されるようになったからだと考えられます。
検索回数や記事表示回数
Elasticsearch導入後は、(ヘルプセンターへの導線を強化した効果もあって)訪問者数が10%増加した一方、ページビュー数は27.3%増加し、サイトの検索性向上による結果が早くも生まれました。
また、検索回数も、導入前1ヶ月間は1,235回だったのに対して、1,932回と56%増加しました。
機能数が着実に増加しているのに対して、出荷1,000件あたりの問い合わせ件数も一定の水準で維持されており、セルフサービスの構築に効果があったものと見ています。
まとめ
Elasticsearch導入によって想定以上に検索体験が向上し、「いままでの検索は一体なんだったのか」と思えるほど嬉しい結果が得られました。また、導入完了までの工数は約3人日だったので、大した工数をかけずレバレッジの効いた開発を行えました。
ロジごまもこの通りご機嫌の結果となりました!
一緒に働く仲間を募集しています
LOGILESSのエンジニアチームはCTO 1名、業務委託 1名、インターン 2名と小さなチームですが、全員がエンジニアリングをビジネス課題や社会課題の解決の手段として捉え、広い視野を持って、アプリケーションが顧客やチームに提供する価値にフォーカスしています。
エンジニア以外のメンバーも含め、多様なバックグラウンドをもち、互いをリスペクトしながら対等な立場で議論し、建設的にビジネスを前に進めようとしています。
ぜひお気軽にご応募ください!