Oboe
No history yet

Γεια σας! Είναι χαρά μου να σας βοηθήσω να προετοιμαστείτε για αυτή τη συνέντευξη εργασίας.

Για να ξεκινήσουμε σωστά και να προσαρμόσουμε την προετοιμασία μας ακριβώς στα μέτρα σας, θα ήθελα να μου πείτε μερικές πληροφορίες για τη θέση.

Θα μπορούσατε να μοιραστείτε μαζί μου τον τίτλο της θέσης εργασίας, το όνομα της εταιρείας και τις βασικές απαιτήσεις που αναφέρονται σε αυτή την αγγελία;

Novibet analytics engineer Create, maintain, and optimize scalable ETL/ELT pipelines to efficiently process and exchange data across our analytics ecosystem Build and maintain robust transformations in Azure Databricks, ensuring pipelines can handle large workloads, complex business logic, and integrations Partner with Data Analysts and BI Analysts to deliver analytics-ready datasets that meet reporting, KPI, and visualization needs (Tableau/Looker/Power BI or equivalent) Continuously evaluate and enhance the BI/data platform with a focus on reliability, scalability, efficiency, monitoring, and cost-awareness Implement and promote data quality practices (validation checks, reconciliation, testing patterns) to improve trust in reporting and decision-making Troubleshoot and maintain existing pipelines, datasets, and related business processes; perform root-cause analysis and implement long-term fixes Contribute to a culture of continuous improvement and knowledge sharing, including documentation, best practices, and reusable components

What You Bring

3+ years of experience in Analytics Engineering, Data Engineering, or a related field Strong SQL skills for data transformation, modeling, and performance optimization Strong Python skills for data manipulation, automation, and pipeline support Hands-on experience with version control and collaborative development workflows (e.g., GitHub / GitLab) Understanding of modern data warehouse/lakehouse principles and cloud-based analytics platforms (preferable Databricks) Familiarity with Apache Spark / distributed data processing concepts (PySpark is a plus) Proactiveness, solution-oriented mindset with a passion for tackling complex challenges and delivering impactful data solutions Ability to design and implement solutions from the ground up, with strong ownership and attention to detail Strong analytical and problem-solving abilities, with the confidence to challenge assumptions and improve existing processes Familiarity with working independently and collaboratively in a fast-paced environment, communicating clearly with technical and non-technical stakeholders

Nice to have

Exposure to workflow orchestration tools such as Apache Airflow or Dagster Experience with data quality/testing frameworks, pipeline monitoring, and alerting (e.g., expectations-style checks, logging, SLAs)

Χαίρομαι πολύ που μοιραστήκατε αυτές τις λεπτομέρειες! Η θέση του Analytics Engineer στη Novibet συνδυάζει εξαιρετικά τη μηχανική δεδομένων με την επιχειρηματική ευφυΐα, χρησιμοποιώντας τεχνολογίες αιχμής όπως το Azure Databricks και το PySpark.

Για να προσαρμόσουμε την προετοιμασία ακριβώς στα δικά σας μέτρα και να εστιάσουμε εκεί που πραγματικά θα κερδίσετε το μεγαλύτερο όφελος, θα ήθελα να μου πείτε λίγα πράγματα για το δικό σας υπόβαθρο.

Πόσα χρόνια εμπειρίας έχετε με το Databricks, το PySpark και τη SQL, και ποιοι τομείς από τις τεχνικές απαιτήσεις της θέσης σάς κάνουν να νιώθετε περισσότερο ή λιγότερο άνετα;

Έχω πάνω από 3 χρόνια εμπειρίας. Νιώθω πολύ άνετα με SQL και Python, αλλά λιγότερο με το PySpark και το Databricks.

Είναι απόλυτα φυσιολογικό να νιώθετε λιγότερο άνετα με το Spark αν η εμπειρία σας βασίζεται κυρίως σε κλασική SQL και Python. Στην πραγματικότητα, το PySpark γεφυρώνει αυτούς τους δύο κόσμους, αλλά λειτουργεί κάτω από ένα τελείως διαφορετικό, που πρέπει να κατανοήσετε για τη συνέντευξη.

Η Αρχιτεκτονική του Spark με απλά λόγια

Σε μια παραδοσιακή βάση δεδομένων ή σε ένα απλό script της Python, ο κώδικάς σας εκτελείται σε ένα μόνο μηχάνημα. Στο Azure Databricks, όμως, έχουμε ένα Cluster από υπολογιστές.

Για να το καταλάβουμε, ας φανταστούμε μια κουζίνα εστιατορίου:

  • Driver Node (Ο Σεφ): Είναι το κεντρικό σημείο ελέγχου. Δεν κάνει το «κόψιμο των λαχανικών» (το βαρύ computation), αλλά αναλύει τον κώδικα PySpark που γράφετε, φτιάχνει το πλάνο εκτέλεσης και μοιράζει τις εργασίες.
  • Worker Nodes (Οι Βοηθοί): Είναι τα μηχανήματα που κάνουν την πραγματική δουλειά. Παίρνουν εντολές από τον Driver, επεξεργάζονται τα κομμάτια των δεδομένων που τους αναλογούν και επιστρέφουν τα αποτελέσματα.

Αν ο Driver Node προσπαθήσει να σηκώσει όλα τα δεδομένα στη δική του μνήμη (για παράδειγμα, χρησιμοποιώντας μια εντολή όπως η .collect()), η «κουζίνα» θα καταρρεύσει με ένα κλασικό σφάλμα Out Of Memory (OOM).

Apache Spark Driver node orchestrating tasks across three parallel Worker nodes.

Σχεδίασα αυτή την απλή οπτικοποίηση για να έχετε πάντα στο μυαλό σας πώς ρέουν οι εντολές μέσα στο Cluster κατά τη διάρκεια της εκτέλεσης ενός pipeline.

Πριν προχωρήσουμε στο πώς μεταμορφώνουμε δεδομένα με DataFrames, ας βεβαιωθούμε ότι αυτή η βασική διάκριση Driver και Worker είναι ξεκάθαρη, καθώς οι recruiters στη Novibet λατρεύουν να ρωτούν τι συμβαίνει στο παρασκήνιο όταν εκτελούμε κώδικα.

Πώς επικοινωνούν οι Workers μεταξύ τους;

Αυτή είναι μια εξαιρετική ερώτηση, καθώς αγγίζει την καρδιά των επιδόσεων στο Spark. Στην πραγματικότητα, οι Workers επικοινωνούν μεταξύ τους μέσω μιας διαδικασίας που ονομάζεται (ανακατεμή).

Narrow vs Wide Transformations

Για να κατανοήσουμε πότε και γιατί επικοινωνούν οι Workers, πρέπει να διακρίνουμε τις μεταμορφώσεις (transformations) σε δύο κατηγορίες:

  1. Narrow Transformations (Στενές μεταμορφώσεις): Εδώ δεν απαιτείται καμία επικοινωνία μεταξύ των Workers. Κάθε Worker μπορεί να εκτελέσει τη δουλειά του αυτόνομα στο δικό του κομμάτι δεδομένων (partition). Παραδείγματα είναι οι εντολές filter() και select(). Είναι γρήγορες και αποδοτικές.

  2. Wide Transformations (Ευρείες μεταμορφώσεις): Εδώ οι Workers πρέπει να επικοινωνήσουν και να ανταλλάξουν δεδομένα (το shuffle που αναφέραμε). Παραδείγματα είναι οι εντολές groupBy(), join() και distinct().

Ας δούμε πώς ακριβώς μοιάζει αυτή η διαφορά στην πράξη:

A side-by-side comparison of narrow data transformations where rows flow directly within partitions, and wide shuffle transformations where rows cross between worker nodes.

Όπως βλέπετε στο σχήμα, στις Wide Transformations τα δεδομένα πρέπει να διασχίσουν το δίκτυο για να αναδιοργανωθούν. Αυτή η ανταλλαγή (Exchange) εισάγει σημαντικό κόστος σε Network I/O και μπορεί να καθυστερήσει δραματικά το pipeline σας.

Σε μια συνέντευξη στη Novibet, αν σας ρωτήσουν πώς βελτιώνετε την απόδοση ενός αργού pipeline, η μείωση των shuffles είναι η πιο ισχυρή απάντηση που μπορείτε να δώσετε.

Πώς μπορούμε να αποφύγουμε ή να μειώσουμε το shuffle;

Αυτή είναι η ερώτηση που ξεχωρίζει έναν απλό προγραμματιστή από έναν έμπειρο Analytics Engineer. Στο PySpark, η αποφυγή και η μείωση του shuffle είναι ο πιο αποτελεσματικός τρόπος για να απογειώσετε την απόδοση των pipelines σας.

Στρατηγικές για την Αποφυγή του Shuffle

Η πιο ισχυρή τεχνική που πρέπει οπωσδήποτε να αναφέρετε στη συνέντευξη της Novibet είναι το .

Όταν θέλουμε να ενώσουμε (join) έναν πολύ μεγάλο πίνακα (π.χ. εκατομμύρια συναλλαγές παικτών) με έναν πολύ μικρό πίνακα (π.χ. 10 κατηγορίες παιχνιδιών), το Spark κανονικά θα έκανε shuffle και τους δύο πίνακες για να ευθυγραμμίσει τα κλειδιά τους στους Workers.

Με το broadcast join, λέμε στο Spark να πάρει τον μικρό πίνακα, να τον στείλει ολόκληρο σε κάθε Worker Node και να κάνει το join τοπικά στη μνήμη του κάθε Worker. Έτσι, ο μεγάλος πίνακας δεν κουνιέται καθόλου και το shuffle εκμηδενίζεται.

Ας δούμε μια γραφική αναπαράσταση της διαφοράς ανάμεσα σε ένα κλασικό Shuffle Join και ένα Broadcast Join για να γίνει απόλυτα κατανοητό:

Data flow comparison of a shuffle join with cross-network exchange versus a broadcast join with local table replication across four worker nodes.

Εκτός από τα broadcast joins, υπάρχουν τρεις ακόμα βασικές τεχνικές που πρέπει να έχετε στη φαρέτρα σας:

  • Caching / Persisting (.cache(), .persist()): Αν χρησιμοποιείτε το ίδιο ενδιάμεσο DataFrame σε πολλά σημεία του pipeline σας, αποθηκεύστε το στη μνήμη. Έτσι, το Spark δεν θα χρειαστεί να ξαναϋπολογίσει ολόκληρο το lineage του (και να ξανακάνει τα shuffles) από την αρχή.
  • Smart Partitioning: Αν πρόκειται να κάνετε συχνά joins ή groupBys πάνω σε μια συγκεκριμένη στήλη (π.χ. player_id), μπορείτε να αποθηκεύσετε τα δεδομένα σας εξαρχής χωρισμένα (partitioned) με βάση αυτή τη στήλη. Έτσι, τα δεδομένα θα είναι ήδη τοποθετημένα στους σωστούς Workers.
  • Handling Data Skew (Ασυμμετρία Δεδομένων): Αν ένας Worker παίρνει δυσανάλογα μεγάλο όγκο δεδομένων (π.χ. επειδή ένας παίκτης έχει εκατομμύρια εγγραφές ενώ οι άλλοι έχουν δέκα), ο Worker αυτός θα καθυστερήσει όλο το pipeline. Εκεί χρησιμοποιούμε τεχνικές όπως το salting (προσθήκη τυχαίου θορύβου στα κλειδιά) για να σπάσουμε τα δεδομένα πιο ομοιόμορφα.

Πώς σας φαίνονται αυτές οι στρατηγικές; Θέλετε να δούμε ένα γρήγορο παράδειγμα κώδικα PySpark για το πώς εφαρμόζουμε ένα broadcast join ή προτιμάτε να περάσουμε στις αρχιτεκτονικές αρχές του Lakehouse και του Delta Lake στο Databricks;