Elasticsearch+Reactでデータの更新が反映されない

2022年6月27日

分析

データベースを操作した後に、以下のようなレスポンスが返されていることを確認した上で、データの再取得を実施。
しかし、登録・削除されたはずのデータが反映されていなかった。

  -> POST http://hogehoge:9200/memo/_doc
  {
    "memo": {
      "message": "テスト",
      "created": "2022-06-27T07:54:29.564Z"
    }
  }
  <- 201
  {
    "_index": "memo",
    "_type": "_doc",
    "_id": "SGsnpIEBPjJae6aOoV1l",
    "_version": 1,
    "result": "created",
    "_shards": {
      "total": 2,
      "successful": 1,
      "failed": 0
    },
    "_seq_no": 162,
    "_primary_term": 1
  }
TRACE: 2022-06-27T12:46:27Z
  -> DELETE http://hogehoge:9200/memo/_doc/SGsnpIEBPjJae6aOoV1l
  
  <- 200
  {
    "_index": "memo",
    "_type": "_doc",
    "_id": "SGsnpIEBPjJae6aOoV1l",
    "_version": 2,
    "result": "deleted",
    "_shards": {
      "total": 2,
      "successful": 1,
      "failed": 0
    },
    "_seq_no": 183,
    "_primary_term": 1
  }

【問題の考察】

  1. サーバ側のデータベースの処理が終わっていない状態なので、更新前のデータを受け取っている
  2. クライアント側のキャッシュが残っていて、悪さをしている(多分違う)

解決策

データベースが更新されるまで、どうにかして待つ!

おまけ

今回、問題が発生したメモ帳では、Submitの前にデータベース操作を行い、その後にSubmitによってページ全体のリロードが実行され、ページを再読み込みするタイミングで、データベースの情報を引き出していた。しかし、これではリロードが早すぎたようで、上記の通りデータベースが更新される前にデータの取得が行われてしまい、古い情報を取得していた。

①DB操作のステータスを参照できるように返り値に設定する

export async function save(type, data) {
    if(type[0] == "mode"){
        const update = await client.update({
            index: index_name,
            id: type[1],
            body: {
                doc:{
                    mode:data.mode,
                    find:data.find
                }
            }
          })

          /**以下を追加 */
          return update;
    }else if(type[0] == "memo"){
        const add = await client.index({
            index: index_name,
            body: {
                memo:{
                    message:data.message,
                    created:data.created
                }
            }
        })

        /**以下を追加 */
        return add;
    }
}

/********** 中略 **********/

export async function del(data_id) {
    const del = await client.delete({
        index: index_name,
        id: data_id
    })

    /**以下を追加 */
    return del;
}

②DB操作が反映されたかをチェックする関数を作成

/**以下を追加 */
async function search_status(data_id) {
    const search = await client.search({
        index: index_name,
        body: {
            query: {
                ids: {
                    values: data_id
                }
            }
        }
    })
    console.log(search);
    return search;
}

/**以下を追加 */
export async function check(data_id, hits){
    const sleep = msec => new Promise(resolve => setTimeout(resolve, msec));

    for (let i = 0; i < 6; i++) {
        try {
            let search = await search_status(data_id);
            if(search.hits.hits.length == hits){
                return true;
            }
        } catch (error) {
            console.log(error);
        }

        await sleep(500);
    }

    return false;
}

③サーバ側でDB操作が完了したのを確認してからリロードするように、呼び出し元のコードを改良

import React, {useState, useEffect} from 'react'

import * as ELController from'./ELController'

export default function AddForm(props) {

    const [message, setMessage] = useState('')
    const doChange = (e)=> {
        setMessage(e.target.value)
    }
    const doAction = (e)=> {

        /**以下を追加 */
        e.preventDefault();
        
        const data = {
            message: message,
            created: new Date()
        }

        const f = async (d) => {
            
            const res = await ELController.save(["memo"], d)
            setMessage(' ')

            /**以下を追加 */
            let check = await ELController.check(res._id, 1);
            if(check){
                window.location.reload();
            }else{
                alert("メモの登録に時間がかかっているか、登録に失敗しました。")
            }

        }
        f(data)
        
    }

    return (
        <form id="add" onSubmit={doAction} action="">
            <div className="form-group row">
                <input type="text" className="form-control-sm col" onChange={doChange} value={message} required />
                <input type="submit" value="Add" className="btn btn-primary btn-sm col-2" />
            </div>
        </form>
    )
}
import React, {useState, useEffect} from 'react'

import * as ELController from'./ELController'

export default function  DelForm(props) {
    const [memo, setMemo] = useState([])
    useEffect(
        () => {
            
            const f = async () => {
                const res = await ELController.load("memo", "")
                setMemo(res)
            }
            f();
        },
        []
    );
    
    const [id, setId] = useState("")
    const doChange = (e)=> {
        setId(e.target.value)
    }
    const doAction = (e)=> {

        /**以下を追加 */
        e.preventDefault();
        
        const f = async (d) => {
            // No1だけ消えない>>IDが入ってない???>>先頭にダミーを置いた
            const res = await ELController.del(d)
            setId("")

            /**以下を追加 */
            let check = await ELController.check(res._id, 0);
            if(check){
                window.location.reload();
            }else{
                alert("メモの削除に時間がかかっているか、削除に失敗しました。")
            }

        }
        f(id);
        
    }
    let items = memo.map((value,key)=>(
        <option key={key} value={value.id}>
            {value.message.substring(0,10)}
        </option>
    ))

    return (
        <form onSubmit={doAction}>
            <div className="form-group row">
                <select onChange={doChange} className="form-control-sm col" defaultValue="-1">
                    <option disabled selected value>削除するメモを選択</option>
                    {items}
                </select>
                <input type="submit" value="Del" className="btn btn-primary btn-sm col-2" />
            </div>
        </form>
    )
}

e.preventDefault();をやって、Submitする(PostとかGetが走る)のをいったん止める。
今回は、別に値をSubmitする必要はないので、DBの操作が反映されたのを確認した段階で、window.location.reload();を実行した。