Firestore Javascript Query Examples by Firefoo

He SEO·2022년 4월 13일

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개의 댓글