Skip to content

Commit

Permalink
perf: Cache Water Body Data to make API Performant
Browse files Browse the repository at this point in the history
- On insert, update and deletion rebuild water body cache
- Cache all water bodies and also cache fishing area wise
- Return cached data if available via public API
  • Loading branch information
marination committed Jul 5, 2023
1 parent 8152d01 commit 5cb1684
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 51 deletions.
70 changes: 20 additions & 50 deletions landa/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@

import frappe

from landa.water_body_management.doctype.water_body.water_body import (
build_water_body_cache,
build_water_body_data,
)


@frappe.whitelist(allow_guest=True, methods=["GET"])
def organization(id: str = None) -> List[Dict]:
Expand Down Expand Up @@ -71,56 +76,21 @@ def organization(id: str = None) -> List[Dict]:
@frappe.whitelist(allow_guest=True, methods=["GET"])
def water_body(id: str = None, fishing_area: str = None) -> List[Dict]:
"""Return a list of water bodies with fish species and special provisions."""
filters = [
["Water Body", "is_active", "=", 1],
["Water Body", "display_in_fishing_guide", "=", 1],
]
if id and isinstance(id, str):
filters.append(["Water Body", "name", "=", id])
if id:
# We do not cache ID since it's uniqueness makes the API performant
return build_water_body_data(id, fishing_area)

if fishing_area and isinstance(fishing_area, str):
filters.append(["Water Body", "fishing_area", "=", fishing_area])
key = fishing_area or "all"
cache_exists = frappe.cache().hexists("water_body_data", key)

if not cache_exists:
# Build the cache (for future calls)
build_water_body_cache(fishing_area)

# return the cached result
return get_water_body_cache(key)

water_bodies = frappe.get_all(
"Water Body",
filters=filters,
fields=[
"name as id",
"title",
"fishing_area",
"fishing_area_name",
"organization",
"organization_name",
"has_master_key_system",
"guest_passes_available",
"general_public_information",
"current_public_information",
"water_body_size as size",
"water_body_size_unit as size_unit",
"location",
],
)

for water_body in water_bodies:
water_body["fish_species"] = frappe.get_all(
"Fish Species Table",
filters={"parent": water_body["id"]},
pluck="fish_species",
)

water_body["special_provisions"] = frappe.get_all(
"Water Body Special Provision Table",
filters={"parent": water_body["id"]},
fields=["water_body_special_provision as id", "short_code"],
)

water_body["organizations"] = frappe.get_all(
"Water Body Management Local Organization",
filters={"water_body": water_body["id"]},
fields=["organization as id", "organization_name"],
)

if water_body.location:
water_body["geojson"] = json.loads(water_body.location)

return water_bodies
def get_water_body_cache(key: str) -> List[Dict]:
"""Return a **CACHED** list of water bodies with fish species and special provisions."""
return frappe.cache().hget("water_body_data", key)
80 changes: 79 additions & 1 deletion landa/water_body_management/doctype/water_body/water_body.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Copyright (c) 2021, Real Experts GmbH and contributors
# For license information, please see license.txt

from typing import Dict, List

import frappe
from frappe import _
Expand All @@ -11,6 +11,12 @@


class WaterBody(Document):
def on_update(self):
self.rebuild_water_body_cache()

def on_trash(self):
self.rebuild_water_body_cache()

def validate(self):
self.validate_edit_access()
self.validate_blacklisted_fish_species()
Expand All @@ -35,6 +41,15 @@ def validate_blacklisted_fish_species(self):
title=_("Invalid Species"),
)

def rebuild_water_body_cache(self):
# Invalidate Cache
frappe.cache().hdel("water_body_data", "all")
frappe.cache().hdel("water_body_data", self.fishing_area)

# Build Cache for all water bodies and fishing area wise
build_water_body_cache()
build_water_body_cache(fishing_area=self.fishing_area)


def remove_outdated_information():
for name in frappe.get_all(
Expand All @@ -49,3 +64,66 @@ def remove_outdated_information():
water_body.current_public_information = None
water_body.current_information_expires_on = None
water_body.save()


def build_water_body_cache(fishing_area: str = None):
"""Build the water body cache for all water bodies **OR** fishing area wise."""
water_bodies = build_water_body_data(fishing_area=fishing_area)
frappe.cache().hset("water_body_data", fishing_area, water_bodies)


def build_water_body_data(id: str = None, fishing_area: str = None) -> List[Dict]:
"""Return a list of water bodies with fish species and special provisions."""
filters = [
["Water Body", "is_active", "=", 1],
["Water Body", "display_in_fishing_guide", "=", 1],
]
if id and isinstance(id, str):
filters.append(["Water Body", "name", "=", id])

if fishing_area and isinstance(fishing_area, str):
filters.append(["Water Body", "fishing_area", "=", fishing_area])

water_bodies = frappe.get_all(
"Water Body",
filters=filters,
fields=[
"name as id",
"title",
"fishing_area",
"fishing_area_name",
"organization",
"organization_name",
"has_master_key_system",
"guest_passes_available",
"general_public_information",
"current_public_information",
"water_body_size as size",
"water_body_size_unit as size_unit",
"location",
],
)

for water_body in water_bodies:
water_body["fish_species"] = frappe.get_all(
"Fish Species Table",
filters={"parent": water_body["id"]},
pluck="fish_species",
)

water_body["special_provisions"] = frappe.get_all(
"Water Body Special Provision Table",
filters={"parent": water_body["id"]},
fields=["water_body_special_provision as id", "short_code"],
)

water_body["organizations"] = frappe.get_all(
"Water Body Management Local Organization",
filters={"water_body": water_body["id"]},
fields=["organization as id", "organization_name"],
)

if water_body.location:
water_body["geojson"] = frappe.parse_json(water_body.location)

return water_bodies

0 comments on commit 5cb1684

Please sign in to comment.