Firestore Javascript Query Examples by Firefoo

He SEO·2022년 4월 13일
0

Firefoo를 이용하여 Javascript query로 firestore data 핸들링 해보자.

The async run function

Admin ADK는 비동기 명령을 하기 위해 Javascript Promise를 사용하며, default run function은 async이다.

생성

Collection.add를 사용한다. 각 요청은 한줄씩 순차적으로 실행되므로 요청이 많다면 오래 걸릴 수 있다.

순차 처리 - 느림😢

async function run() {
  await db.collection("companies").add({name: "Google"})
  await db.collection("companies").add({name: "Apple"})
  await db.collection("companies").add({name: "Microsoft"})
}

병렬 처리😄

병렬 실행하려면 Collection.add의 요청 결과를 배열로 저장하고 Promise.all로 모든 요청이 끝나기를 기다린다.

async function run() {
  // add docs parellel - faster
  const promises = []
  promises.push( db.collection("companies").add({ name: "Google" }) )
  promises.push( db.collection("companies").add({ name: "Apple" }) )
  promises.push( db.collection("companies").add({ name: "Microsoft" }) )
  return await Promise.all(promises)
}

Array.map을 사용하면 식이 좀 더 간결해진다.

async function run() {
  const companies = [{name: "Google"}, {name: "Apple"}, {name: "Microsoft"}]
  const promises = companies.map((data) => db.collection("companies").add(data))
  return await Promise.all(promises)
}

필터링 & 정렬

필터링과 정렬을 위해 whereorderBy를 사용할 수 있다. Timestamp 값을 사용하게 될때 Firestore Timestamp 인스턴스로 변환해야하며, Javascript Date나 unix timestamp는 작동하지 않는다.

const minDate = new Date("1995-12-17T03:24:00")
const maxDate = new Date("2022-04-13T20:24:00")

// Firestore Timestamp 인스턴스로 변환
const minTimestamp = admin.firestore.Timestamp.fromDate(minDate)
const maxTimestamp = admin.firestore.Timestamp.fromDate(maxDate)

async function run() {
  const query = await db.collection("books")
  						.where("rdt", ">", minTimestamp) // 생성일(rdt)이 minDate보다 크고
  						.where("rdt", "<", maxTimestamp) // 생성일(rdt)이 maxDate보다 작고
  						.orderBy("rdt", "desc") // 생성일(rdt)로 정렬
  						.limit(30) //30개까지
  						.get(); // 읽기
  return query
}

로깅

로그는 console.log를 사용한다.

async function run() {
  const query = await db.collection("users").where("lastname", "==", "Kim")
  for (const doc of query.docs) {
    console.log("result is : " + doc.data().lastname)
  }
}

수정

콜렉션의 전체 다큐먼트의 username 필드를 user 필드로 변경한다면, 모든 document를 순차적으로 가져와 하나씩 업데이트 하는 것이 가장 쉬운 방법이지만 데이터가 많다면 느리다.

순차 처리 - 느림😢

async function run() {
  const query = await db.collection("users").get(); // users collection의 전체 데이터 읽기
  
  for (const doc of query.docs) {
    const name = doc.data().name ?? "unnamed"
    await doc.ref.update({
    	username: name, //새로운 필드 생성 및 값 주입
        name: admin.firestore.FieldValue.delete() // 필드 삭제
    })
  }
}

병렬 처리😄

빠르게 사용하려면 병렬로 업데이트해야 한다. Promise.all을 사용하면 전체 update가 끝날 때까지 프로세스가 죽지 않는다.

async function run() {
  const query = await db.collection("users").get()
  const promises = query.docs.map((doc) => {
    const fieldUpdates = {
      username: doc.data().name ?? "unnamed", //새로운 필드 생성 및 값 주입
      name: admin.firestore.FieldValue.delete() // 필드 삭제
    }
    return doc.ref.update(fieldUpdates)
  })
  
  return await Promises.all(promises)
}

배치

요청의 수를 최소화하기 위해 최대 500개의 명령을 batched writes로 사용할 수 있다. 배치는 여러 write 명령을 가질 수 있지만 read 명령은 하지 못한다.

async function run() {
  const query = await db.collection("users").get();

  const docChunks = _.chunk(query.docs, 500);
  for (const docChunk of docChunks) {
    const batch = db.batch();
    for (const doc of docChunk) {
      const fieldUpdates = {
        username: doc.data().name ?? "unnamed",
        name: admin.firestore.FieldValue.delete(),
      }
      batch.update(doc.ref, fieldUpdates);
    }
    console.log(`committing chunk of ${docChunk.length} updates`)
    await batch.commit();
  }
}

Joins

Firestore는 join collection을 지원하지 않는다. 아래의 쿼리를 통해 몇개의 collection에서 join 동작을 수행할 수 있다.

// employees, companies collection 2개의 데이터를 읽어와 필터링하여 join한 것처럼 사용한다
// companies의 경우 수익이 10K 이상인 회사만 추린다
async function run() {
  const employeePromise = db.collection("employees").get();
  const companyPromise = db.collection("companies").where("revenue", ">", 10000).get(); // 수익이 10K 이상인 회사만 필터
  const [employeeQuery, companyQuery] = await Promise.all([employeePromise, companyPromise]);
  // 필터링된 회사의 id를 추출
  const companyDocsById = _.keyBy(companyQuery.docs, (doc) => doc.id);

  for (const employeeDoc of employeeQuery.docs) {
    const employee = employeeDoc.data();
    const companyDoc = companyDocsById[employee.companyId];
    // 사원을 하나씩 확인하며 해당 사원의 회사가 필터링된 회사 리스트에 있다면 로그를 찍고 없으면 다음 사원으로 넘어간다
    if (companyDoc != null) {
      const company = companyDoc.data();
      console.log(`${employee.name} works at ${company.name} (${company.revenue} revenue)`);
    }
  }
}

전역 변수

  • admin : Admin SDK에서 참조하는 변수. Firebase project에서 이미 이니셜라이징 되었다.
  • db : admin.firestore()를 가르킨다.
  • _(locash) : locash library
    • _.chunk(docs.10) : 길이 10의 array에 결과를 나눠서 저장
    • _.groupBy(docs, (d) => d.date().type) : 데이터를 그루핑. SQL의 GROUP BY와 유사
    • _.sum(values), .mean(values), .max(values) : values 배열 값의 합산, 평균, 최대값을 구한다.

참고 사이트

profile
BACKEND 개발 기록 중. 감사합니다 😘

0개의 댓글