Even though we never use tz aware object in frappe this function
previously used to retain them, so keep TZinfo for sake of compat.
Nothing should be affected ideally.
* perf: Reduce penalty for lack of redis connection
If redis isn't running than this client cache is slower than default
implementation because of the extra locking overhead.
* test: update perf redis counts
* perf: cache table columns in client-cache
* fix: race condition on cache-client_cache init
Rare but apparant in synthetic benchmarks.
Cache is set but client cache is still being initialized then request
will fail.
* perf: Don't run notifications when loading document
WHAT?
* fix: use cached doc to repopulate
* perf: reduce get_meta calls
If I have to hazard a guess, 99% API calls are not server scripts, then
why check it first and pay the costs?
This PR first checks if method is a real method in python code and if
it's not found then only attempts to fetch it from server script map.
I'll revert this if I can bring the costs in acceptable limits with
client-side caching.
Currently one test runner takes significantly longer than another. This
is entirely due to test_commands.py which needs to create new site and
do backup/restore tests etc. All of which are far far slower than other
tests.
* perf: No need to set expiry for key everytime
* fix: Set expiry on first request and never again
This prevents problem of rate limiter keys growing constantly.
Call it a new year's resolution.
`frappe/__init__.py` has grown crazy big over time and left unattended it will continue to grow.
This new ~test~ tax will require reducing 3 line per day (so ~1000 in a year) from 1st Jan 2025 onwards. I am offering a headstart of 50 days in this PR by moving ~150 lines: #28869
This is middle ground between caching it completely and requiring a
restart/signal to reload vs always reloading it.
I don't know any use cases that can break from this, nowhere in code
configs should be expected to reload instantly.
This change is only applied to requests for now
* perf: reuse current time
now_datetime is site-tz-aware, we don't need it here.
* perf: dont need redis transactions
* perf: use `time.time()` instead of datetime
Using `datetime.timestamp()` is a round-about way to use `time.time()`
with extra cost of dealing with datetime and timezones.
* perf: define slots for rate_limiter
* fix!: Remove used rate limit header
This just shares how much was consumed in current request, people can
just time requests to get an approximation for this, not sure why is this
useful.
* chore: remove verbose output from test runner
This is same output that's shared by test runner in different format?
This makes it annoying to scroll through when just running single test
locally.
* fix: Remove clutter from test output
Test records don't change after first run.
Tests are executed many many times locally
* test: retry flaky postgres backup tests
Python's multithreaded model is _inefficient_ because of Global
Interpreter Lock (GIL). Any one thread of process can run at any given
time. Thus only valid use case for threads in Python are:
1. Hiding I/O latency by switching to a different thread.
2. Using compiled extensions that yield GIL for long enough time to do
meaningful work in other threads.
Both of these are not as frequent as you'd imagine and gthread worker
with multiple threads often just end up contending on lock and waste
useful CPU cycles doing nothing. Pinning worker process to a core nearly
eliminates this contention wastage. This waste can be 5-10% and goes up
sharply with more threads.
E.g. FC typically has maxed out config of 24 workers which allows
"accepting" and working on 24 requests at a time. But that doesn't mean
24 requests are on CPU at any given time, that would require 24 physical
cores.
Why do this?
1. Context switching in threads is faster than switching process - fewer
cache misses, fewer TLB misses etc.
2. The model is simple
True parallelism = count(cores) = count(processes).
Expected concurrency = count(processes) * count(threads).
3. This is far simpler to reason about than something like async
executor model.
4. Ability to queue more requests than what can be handled is already
implemented by `bind(2)` and `accept(2)` in kernel. There is no real
benefit of accepting 1000 requests if you can only work on 20 of them
at a time. This is because we do a lot of "work" in requests, it's
not just issuing an external request and waiting for it.
5. We can achieve practically same concurrency as 24 workers with 4
process x 6 threads. That's a lot of memory saved to run other useful
things.
Caveats:
- This kind of pinning can potentially make Linux scheduler inefficient.
I don't quite think it's going to be a big problem because there are
plenty of other things to run which a core can steal from other core
if it doesn't have enough work.
- Load balancing in single-server multi-bench setup. I *think* by nature
of how `accept(2)` works, load balancing will still happen pretty much
automatically. If certain core is overloaded, naturally other cores
will reach `accept(2)` more frequently and take the load off of that
core. This is something worth validating in practice by creating
skewed affinities.
- This code is not NUMA-aware. None of our machines have NUMA nodes so,
I am ignoring it. Don't use it if you have a NUMA setup.
- If new CPUs are hotplugged or existing ones are disabled then it can
be inefficient (worse than current) until that worker auto-restarts (which
happens after N requests in FC setup).
Ideal solution: We write userspace scheduler to implement
"soft-affinity" using Linux's new eBPF based sched_ext feature. That's
too much extra work but I'll consider this too at some point.
closes https://github.com/frappe/caffeine/issues/13
* chore(typing): type filters
* chore(typing): type filters for get_list et al
* fix: dashboard chart filter expression
* test: fix case with new-style right hand object to equality check
* chore: place new typed filter under typing verification
* chore: remove debug print statment
* chore: inverse logic of type guard
* fix: add float to filter value types
* chore: clarify value naming