{%hackmd @wh91008/theme %} # Lecture 3 - Firestore讀寫與Query計算方式 ###### tags: `GCP` `Firestore` `Python` [TOC] --- ## ==一、前言== 除了學會 Firestore 的使用外,由於 Firestore 會有流量的限制,如果流量過量也需要多付錢,所以本章節主要是用來討論三大問題: 1. Firestore在免費版中有哪些流量的限制? 2. Firestore的讀寫次數應如何計算與監控? 3. 該如何來做流量的節省? --- ## ==二、Firestore的使用量與限制== 可直接參考[Firebase官網](https://firebase.google.com/docs/firestore/quotas?hl=zh-cn)所提供的資料 --- ## ==三、Firestore讀寫次數的監控與計算== ### (一)Firestore讀寫次數的監控 對於監控Firestore讀寫次數的方法,主要有三種: 1. Firebase控制台 - **介紹** 即Firebase內建的控制台,可於 **Database->用量** 中查詢。 - **缺點** 觀看用量變化的介面不夠細緻,沒辦法觀看小時間區間的用量變化,而且數值是採取累加式,如果想知道數據的變化,還得自己用加減法來計算。  2. Google Cloud Platform(即GCP) - **介紹** 於[第三章 - Cloud Function的入門與部署](https://hackmd.io/yn10QCzJS8yqbe8twZGCFQ)已介紹函式的佈署,在成功佈署連結GCP後,就可從GCP中查看Firestore用量的變化。 - **缺點** 均使用文字與數字來描述資料的讀寫量,沒有讓人方便觀看的視覺化圖形。  3. Stackdriver Monitoring 可以用來解決前面兩種方法的缺點,在Stackdriver Monitoring可以從GCP中收集可用來創建視覺化圖形的資料,並寫以更細微的表現來繪製。 創建的方式和詳細資訊可直接參考 [Google Cloud官方教學]( https://cloud.google.com/firestore/docs/monitor-usage#stackdriver-monitoring) ### (二)Firestore讀寫次數的計算 在 Firestore 裡面讀寫任何資料的基準點都是以 **document** 來做計算,也就是 **「讀寫了多少個路徑尾端的document」**。 1. **寫入數** - set、update 都算一次寫入 - 假使set三次,不論是寫在同一個document三次,或是寫入不同的三個document,都是增加三筆寫入數。 - 如果一次set一堆資料進同一個document的欄位,寫入數一樣是增加一次 - 寫入的次數是以路徑尾端的document做基準 - 寫入是看最後一個document,例如寫入的路徑呈現如下,則寫入是增加一次(雖然路徑中的match也是document,但他只是路徑中的一員,所以並不會增加到寫入的次數。 ```db.collection('message',"match","round01").document('doc_name')``` 2. **讀取數** - 讀取一個document就會增加一次的讀取,跟寫入的意思一樣 舉例來說,如果你有 1 個 collection 裡頭有 5 個 document,使用 get 取得這個 collection 時等同於讀取 5 次。 補:但如果指定 document 的路徑讀取就可以單純讀取一次,不過若你進後台打開來看資料,也會再度讀取 5 次。 - 應該減少直接從網頁瀏覽 Firestore 讀取的數量和 doc 的數量有關,並且「讀取數」也包含了自己從 firestore 後台把資料庫打開的次數。因此網頁瀏覽也會讓讀取次數增加,並且因為所有的資料都顯示出來,所以讀取次數會大大增加。 例如資料的呈現如附圖,則讀取次數增加14  ###### :book: 參考資料: ###### 1. Monitoring usage https://cloud.google.com/firestore/docs/monitor-usage ###### 2. Stackdriver Monitoring https://app.google.stackdriver.com/ ###### 3. 查看 Cloud Firestore 价格示例 https://firebase.google.com/docs/firestore/billing-example?hl=zh-cn#read-chats --- ## ==四、Firestore的流量節省(Query)== ### (一)Query簡介 首先我們必須先了解:「什麼是Query?」、「Query可以用來做什麼?」,事實上Query是Firestore提供的一種查詢方式,甚至可與 ```get()``` 或 ```addSnapshotListener()``` 等語法搭配使用。因此Query的語法可以有效的抓取各種所需的資料(也近似於SQL語法的查詢功能),進而限制手機端的讀寫次數,節省資源的損耗。 ### (二)Query的語法 從上面的介紹我們會開始好奇,那Query提供的查詢語法有那些呢? 在官方的解釋中,主要能統整出以下用法(節錄Python語法,若要觀看其他語言的語法則可觀看參考資料): 1. **查找條件** - <code>where()</code>:與SQL語法中的```where```相同,透過「某條件篩選」集合中的文件。 - <code>orderBy()</code>:與SQL語法中的```order by```相同,可將資料依指定欄位排序。 - <code>limit()</code>:與SQL語法中的```limit```相同,用來限制獲取資料的筆數。 2. 查詢游標 - 含標準值 - <code>start_at()</code>:從指定的 Doc 或欄位的某值(含)當起始點獲取資料。 - <code>end_at()</code>:從指定的 Doc 或欄位的某值(含)當終點獲取資料。 - 不含標準值 - <code>start_after()</code>:從指定的 Doc 或欄位的某值(不含)當起始點獲取資料。 - <code>end_before()</code>:從指定的 Doc 或欄位的某值(不含)當終點獲取資料。 ### (三)Query語法實作案例 官方釋出的參考例子為: ```python= class City(object): def __init__(self, name, state, country, capital=False, population=0, regions=[]): self.name = name self.state = state self.country = country self.capital = capital self.population = population self.regions = regions @staticmethod def from_dict(source): # ... def to_dict(self): # ... def __repr__(self): return( u'City(name={}, country={}, population={}, capital={}, regions={})' .format(self.name, self.country, self.population, self.capital, self.regions)) cities_ref = db.collection(u'cities') cities_ref.document(u'SF').set( City(u'San Francisco', u'CA', u'USA', False, 860000, [u'west_coast', u'norcal']).to_dict()) cities_ref.document(u'LA').set( City(u'Los Angeles', u'CA', u'USA', False, 3900000, [u'west_coast', u'socal']).to_dict()) cities_ref.document(u'DC').set( City(u'Washington D.C.', None, u'USA', True, 680000, [u'east_coast']).to_dict()) cities_ref.document(u'TOK').set( City(u'Tokyo', None, u'Japan', True, 9000000, [u'kanto', u'honshu']).to_dict()) cities_ref.document(u'BJ').set( City(u'Beijing', None, u'China', True, 21500000, [u'hebei']).to_dict()) ``` 1. ```where(fieldPath, opStr, value)``` ```python= # Create a reference to the cities collection cities_ref = db.collection(u'cities') ###################### Simple queries "where" ###################### # The following query returns all cities with state "CA" query_ref = cities_ref.where(u'state', u'==', u'CA') # The following query returns all the "capital cities" query = cities_ref.where(u'capital', u'==', True) ###################### Execute a query "where" ###################### docs = db.collection(u'cities').where(u'capital', u'==', True).stream() for doc in docs: print(u'{} => {}'.format(doc.id, doc.to_dict())) ###################### Query operators "where" ###################### #The comparison can be <, <=, ==, >, >=, or array-contains cities_ref.where(u'state', u'==', u'CA') cities_ref.where(u'population', u'<', 1000000) cities_ref.where(u'name', u'>=', u'San Francisco') cities_ref.where(u'regions', u'array_contains', u'west_coast') ###################### Compound queries "where" ###################### sydney_query = cities_ref.where( u'state', u'==', u'CO').where(u'name', u'==', u'Denver') large_us_cities_query = cities_ref.where( u'state', u'==', u'CA').where(u'population', u'>', 1000000) ################## Collection group queries "where" ################## # you can create a landmarks collection group by adding a landmarks subcollection to each city sf_landmarks = cities.document(u'SF').collection(u'landmarks') sf_landmarks.document().set({ u'name': u'Golden Gate Bridge', u'type': u'bridge' }) sf_landmarks.document().set({ u'name': u'Legion of Honor', u'type': u'museum' }) la_landmarks = cities.document(u'LA').collection(u'landmarks') la_landmarks.document().set({ u'name': u'Griffith Park', u'type': u'park' }) la_landmarks.document().set({ u'name': u'The Getty', u'type': u'museum' }) dc_landmarks = cities.document(u'DC').collection(u'landmarks') dc_landmarks.document().set({ u'name': u'Lincoln Memorial', u'type': u'memorial' }) dc_landmarks.document().set({ u'name': u'National Air and Space Museum', u'type': u'museum' }) ``` 2. ```order_by()``` and ```limit()``` ```python= cities_ref = db.collection(u'cities') # 以cities集合中的name來做升冪排序,並且限制為只取三筆 db.collection(u'cities').order_by(u'name').limit(3).stream() # # 若改為降冪排序的版本 query = cities_ref.order_by(u'name', direction=firestore.Query.DESCENDING).limit(3) results = query.stream() # 也可搭配where使用 query = cities_ref.where( u'population', u'>', 2500000).order_by(u'population') results = query.stream() ``` :::warning :bulb: 小提醒:程式碼中的 ```stream()``` 可以直接想像為 "Query"版本的 ```get()```,用法也大同小異 ::: 3. Paginate data with query cursors(查詢游標) ```python= cities_ref = db.collection(u'cities') ###################### start_at ###################### query_start_at = cities_ref.order_by(u'population').start_at({ u'population': 1000000 }) doc_ref = db.collection(u'cities').document(u'SF') snapshot = doc_ref.get() start_at_snapshot = db.collection( u'cities').order_by(u'population').start_at(snapshot) ###################### end_at ###################### query_end_at = cities_ref.order_by(u'population').end_at({ u'population': 1000000 }) ###################### start_after ###################### # 結合order_by、limit、start_after cities_ref = db.collection(u'cities') first_query = cities_ref.order_by(u'population').limit(3) # Get the last document from the results docs = first_query.stream() last_doc = list(docs)[-1] # Construct a new query starting at this document # Note: this will not have the desired effect if # multiple cities have the exact same population value last_pop = last_doc.to_dict()[u'population'] next_query = ( cities_ref .order_by(u'population') .start_after({ u'population': last_pop }) .limit(3) ) # Use the query for pagination # ... # To start at a specific Springfield, you could add the state as a secondary condition in your cursor clause. start_at_name = ( db.collection(u'cities') .order_by(u'name') .order_by(u'state') .start_at({ u'name': u'Springfield' }) ) start_at_name_and_state = ( db.collection(u'cities') .order_by(u'name') .order_by(u'state') .start_at({ u'name': u'Springfield', u'state': u'Missouri' }) ) ``` ### (四)Query的計算方式 1. **Query的查找不計算次數,而是計算Document數量** Query的查找本身並不計算次數,而是計算使用了Query語法後所產出的Document數量來增加讀取次數(就像前面所說的,Firestore主要是依據Document的讀取來做為讀取次數的依據)。 2. **善用Query語法節省讀取數量** 因為主要讀取次數是受到Document數量來做改變,因此可以善用Query的語法來做為最終Document產出的數量限制(例如以```order_by```將timestamp做排序後,再以```limit```篩選最新的幾筆。 ###### :book: 參考資料: ###### 1. Cloud Firestore β (14): 進階查詢 - 1 https://ithelp.ithome.com.tw/articles/10208494 ###### 2. where https://firebase.google.com/docs/firestore/query-data/queries ###### 3. Order and limit https://firebase.google.com/docs/firestore/query-data/order-limit-data ###### 4. Paginate data with query cursors https://firebase.google.com/docs/firestore/query-data/query-cursors
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up